1. 程式人生 > >設計模式學習總結(超讚!!!)

設計模式學習總結(超讚!!!)

我是技術搬運工,好東西當然要和大家分享啦.原文地址

第 1 章 設計模式入門

1. 設計模式概念

設計模式不是程式碼,而是解決問題的方案,學習現有的設計模式可以做到經驗複用。

擁有設計模式詞彙,在溝通時就能用更少的詞彙來討論,並且不需要了解底層細節。

2. 問題描述

設計不同種類的鴨子擁有不同的叫聲和飛行方式。

3. 簡單實現方案

使用繼承的解決方案如下,這種方案程式碼無法複用,如果兩個鴨子類擁有同樣的飛行方式,就有兩份重複的程式碼。

4. 設計原則

封裝變化在這裡變化的是鴨子叫和飛行的行為方式。

針對介面程式設計,而不是針對實現程式設計 變數宣告的型別為父類,而不是具體的某個子類。父類中的方法實現不在父類,而是在各個子類。程式在執行時可以動態改變變數所指向的子類型別。

運用這一原則,將叫和飛行的行為抽象出來,實現多種不同的叫和飛行的子類,讓子類去實現具體的叫和飛行方式。

多用組合,少用繼承 組合也就是 has-a 關係,通過組合,可以在執行時動態改變實現,只要通過改變父類物件具體指向哪個子類即可。而繼承就不能做到這些,繼承體系在建立類時就已經確定。

運用這一原則,在 Duck 類中組合 FlyBehavior 和 QuackBehavior 類,performQuack() 和 performFly() 方法委託給這兩個類去處理。通過這種方式,一個 Duck 子類可以根據需要去例項化 FlyBehavior 和 QuackBehavior 的子類物件,並且也可以動態地進行改變。

5. 整體設計圖

6. 模式定義

策略模式 :定義了演算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓演算法的變化獨立於使用演算法的客戶。

7. 實現程式碼

public abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;

    public Duck(){
    }

    public void performFly(){
        flyBehavior.fly();
    }

    public void setFlyBehavior(FlyBehavior
fb){ flyBehavior = fb; } public void performQuack(){ quackBehavior.quack(); } public void setQuackBehavior(QuackBehavior qb){ quackBehavior = qb; } }
public class MallarDuck extends Duck{
    public MallarDuck(){
        flyBehavior = new FlyWithWings();
        quackBehavior = new Quack();
    }
}
public interface FlyBehavior {
    void fly();
}
public class FlyNoWay implements FlyBehavior{
    @Override
    public void fly() {
        System.out.println("FlyBehavior.FlyNoWay");
    }
}
public class FlyWithWings implements FlyBehavior{
    @Override
    public void fly() {
        System.out.println("FlyBehavior.FlyWithWings");
    }
}
public interface QuackBehavior {
    void quack();
}
public class Quack implements QuackBehavior{
    @Override
    public void quack() {
        System.out.println("QuackBehavior.Quack");
    }
}
public class MuteQuack implements QuackBehavior{
    @Override
    public void quack() {
        System.out.println("QuackBehavior.MuteQuack");
    }
}
public class Squeak implements QuackBehavior{
    @Override
    public void quack() {
        System.out.println("QuackBehavior.Squeak");
    }
}
public class MiniDuckSimulator {
    public static void main(String[] args) {
        Duck mallarDuck = new MallarDuck();
        mallarDuck.performQuack();
        mallarDuck.performFly();
        mallarDuck.setFlyBehavior(new FlyNoWay());
        mallarDuck.performFly();
    }
}

執行結果

QuackBehavior.Quack
FlyBehavior.FlyWithWings
FlyBehavior.FlyNoWay

第 2 章 觀察者模式

1. 模式定義

定義了物件之間的一對多依賴,當一個物件改變狀態時,它的所有依賴者都會受到通知並自動更新。主題(Subject)是被觀察的物件,而其所有依賴者(Observer)成為觀察者。

2. 模式類圖

主題中具有註冊和移除觀察者,並通知所有註冊者的功能,主題是通過維護一張觀察者列表來實現這些操作的。

觀察者擁有一個主題物件的引用,因為註冊、移除還有資料都在主題當中,必須通過操作主題才能完成相應功能。

3. 問題描述

天氣資料佈告板會在天氣資訊發生改變時更新其內容,佈告板有多個,並且在將來會繼續增加。

4. 解決方案類圖

5. 設計原則

為互動物件之間的鬆耦合設計而努力 當兩個物件之間鬆耦合,它們依然可以互動,但是不太清楚彼此的細節。由於鬆耦合的兩個物件之間互相依賴程度很低,因此係統具有彈性,能夠應對變化。

6. 實現程式碼

public interface Subject {
    public void resisterObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObserver();
}
import java.util.ArrayList;
import java.util.List;

public class WeatherData implements Subject {
    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
        observers = new ArrayList<>();
    }

    @Override
    public void resisterObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        int i = observers.indexOf(o);
        if (i >= 0) {
            observers.remove(i);
        }
    }

    @Override
    public void notifyObserver() {
        for (Observer o : observers) {
            o.update(temperature, humidity, pressure);
        }
    }

    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        notifyObserver();
    }
}
public interface Observer {
    public void update(float temp, float humidity, float pressure);
}
public class CurrentConditionsDisplay implements Observer {
    private Subject weatherData;

    public CurrentConditionsDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.resisterObserver(this);
    }

    @Override
    public void update(float temp, float humidity, float pressure) {
        System.out.println("CurrentConditionsDisplay.update:" + temp + " " + humidity + " " + pressure);
    }
}
public class StatisticsDisplay implements Observer {
    private Subject weatherData;

    public StatisticsDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.resisterObserver(this);
    }

    @Override
    public void update(float temp, float humidity, float pressure) {
        System.out.println("StatisticsDisplay.update:" + temp + " " + humidity + " " + pressure);
    }
}
public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);

        weatherData.setMeasurements(0, 0, 0);
        weatherData.setMeasurements(1, 1, 1);
    }
}

執行結果

CurrentConditionsDisplay.update:0.0 0.0 0.0
StatisticsDisplay.update:0.0 0.0 0.0
CurrentConditionsDisplay.update:1.0 1.0 1.0
StatisticsDisplay.update:1.0 1.0 1.0

第 3 章 裝飾模式

1. 問題描述

設計不同種類的飲料,並且每種飲料可以動態新增新的材料,比如可以新增牛奶。計算一種飲料的價格。

2. 模式定義

動態地將責任附加到物件上。在擴充套件功能上,裝飾者提供了比繼承更有彈性的替代方案。

下圖中 DarkRoast 物件被 Mocha 包裹,Mocha 物件又被 Whip 包裹,並且他們都繼承自相同父類,都有 cost() 方法,但是外層物件的 cost() 方法實現呼叫了內層物件的 cost() 方法。因此,如果要在 DarkRoast 上新增 Mocha,那麼只需要用 Mocha 包裹 DarkRoast,如果還需要 Whip ,就用 Whip 包裹 Mocha,最後呼叫 cost() 方法能把三種物件的價格都包含進去。

3. 模式類圖

裝飾者和具體元件都繼承自元件型別,其中具體元件的方法實現不需要依賴於其它物件,而裝飾者擁有一個元件型別物件,這樣它可以裝飾其它裝飾者或者具體元件。所謂裝飾,就是把這個裝飾者套在被裝飾的物件之外,從而動態擴充套件被裝飾者的功能。裝飾者的方法有一部分是自己的,這屬於它的功能,然後呼叫被裝飾者的方法實現,從而也保留了被裝飾者的功能。可以看到,具體元件應當是裝飾層次的最低層,因為只有具體元件有直接實現而不需要委託給其它物件去處理。

4. 問題解決方案的類圖

5. 設計原則

**類應該對擴充套件開放,對修改關閉。**也就是新增新功能時不需要修改程式碼。在本章問題中該原則體現在,在飲料中新增新的材料,而不需要去修改飲料的程式碼。觀察則模式也符合這個原則。不可能所有類都能實現這個原則,應當把該原則應用於設計中最有可能改變的地方。

6. Java I/O 中的裝飾者模式

7. 程式碼實現

public interface Beverage {
    public double cost();
}
public class HouseBlend implements Beverage{
    @Override
    public double cost() {
        return 1;
    }
}
public class DarkRoast implements Beverage{
    @Override
    public double cost() {
        return 1;
    }
}
public abstract class CondimentDecorator implements Beverage{
    protected Beverage beverage;
}
public class Mocha extends CondimentDecorator {

    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public double cost() {
        return 1 + beverage.cost();
    }
}
public class Milk extends CondimentDecorator {

    public Milk(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public double cost() {
        return 1 + beverage.cost();
    }
}
public class StartbuzzCoffee {
    public static void main(String[] args) {
        Beverage beverage = new HouseBlend();
        beverage = new Mocha(beverage);
        beverage = new Milk(beverage);
        System.out.println(beverage.cost());
    }
}

輸出

3.0

第 4 章 工廠模式

4.1 簡單工廠

1. 問題描述

有不同的 Pizza,根據不同的情況用不同的子類例項化一個 Pizza 物件。

2. 定義

簡單工廠不是設計模式,更像是一種程式設計習慣。在例項化一個超類的物件時,可以用它的所有子類來進行例項化,要根據具體需求來決定使用哪個子類。在這種情況下,把例項化的操作放到工廠來中,讓工廠類來決定應該用哪個子類來例項化。這樣做把客戶物件和具體子類的實現解耦,客戶物件不再需要知道有哪些子類以及例項化哪個子類。因為客戶類往往有多個,如果不使用簡單工廠,那麼所有的客戶類都要知道所有子類的細節,一旦子類發生改變,例如增加子類,那麼所有的客戶類都要發生改變。

3. 解決方案類圖

4. 程式碼實現

public interface Pizza {
    public void make();
}
public class CheesePizza implements Pizza{
    @Override
    public void make() {
        System.out.println("CheesePizza");
    }
}
public class GreekPizza implements Pizza{
    @Override
    public void make() {
        System.out.println("GreekPizza");
    }
}
public class SimplePizzaFactory {
    public Pizza createPizza(String type) {
        if (type.equals("cheese")) {
            return new CheesePizza();
        } else if (type.equals("greek")) {
            return new GreekPizza();
        } else {
            throw new UnsupportedOperationException();
        }
    }
}
public class PizzaStore {
    public static void main(String[] args) {
        SimplePizzaFactory simplePizzaFactory = new SimplePizzaFactory();
        Pizza pizza = simplePizzaFactory.createPizza("cheese");
        pizza.make();
    }
}

執行結果

CheesePizza

4.2 工廠方法模式

1. 問題描述

每個地區的 Pizza 店雖然種類相同,但是都有自己的風味,需要單獨區分。例如,一個客戶點了紐約的 cheese 種類的 Pizza 和在芝加哥點的相同種類的 Pizza 是不同的。

2. 模式定義

定義了一個建立物件的介面,但由子類決定要例項化的類是哪一個。工廠方法讓類把例項化推遲到子類。

3. 模式類圖

在簡單工廠中,建立物件的是另一個類,而在工廠方法中,是由子類來建立物件。工廠方法常用在在每個子類都有自己的一組產品類。可以為每個子類建立單獨的簡單工廠,但是把簡單工廠中建立物件的程式碼放到子類中來可以減少類的數目,因為子類不算是產品類,因此完全可以這麼做。

4. 解決方案類圖

5. 程式碼實現

public interface Pizza {
    public void make();
}
public interface PizzaStore {
    public Pizza orderPizza(String item);
}
public class NYStyleCheesePizza implements Pizza{
    @Override
    public void make() {
        System.out.println("NYStyleCheesePizza is making..");
    }
}
public class NYStyleVeggiePizza implements Pizza {
    @Override
    public void make() {
        System.out.println("NYStyleVeggiePizza is making..");
    }
}
public class ChicagoStyleCheesePizza implements Pizza{
    @Override
    public void make() {
        System.out.println("ChicagoStyleCheesePizza is making..");
    }
}
public class ChicagoStyleVeggiePizza implements Pizza{
    @Override
    public void make() {
        System.out.println("ChicagoStyleVeggiePizza is making..");
    }
}
public class NYPizzaStore implements PizzaStore {
    @Override
    public Pizza orderPizza(String item) {
        Pizza pizza = null;
        if (item.equals("cheese")) {
            pizza = new NYStyleCheesePizza();
        } else if (item.equals("veggie")) {
            pizza = new NYStyleVeggiePizza();
        } else {
            throw new UnsupportedOperationException();
        }
        pizza.make();
        return pizza;
    }
}
public class ChicagoPizzaStore implements PizzaStore {
    @Override
    public Pizza orderPizza(String item) {
        Pizza pizza = null;
        if (item.equals("cheese")) {
            pizza = new ChicagoStyleCheesePizza();
        } else if (item.equals("veggie")) {
            pizza = new ChicagoStyleVeggiePizza();
        } else {
            throw new UnsupportedOperationException();
        }
        pizza.make();
        return pizza;
    }
}
public class PizzaTestDrive {
    public static void main(String[] args) {
        PizzaStore nyStore = new NYPizzaStore();
        nyStore.orderPizza("cheese");
        PizzaStore chicagoStore = new ChicagoPizzaStore();
        chicagoStore.orderPizza("cheese");
    }
}

執行結果

NYStyleCheesePizza is making..
ChicagoStyleCheesePizza is making..

4.3 抽象工廠模式

1. 設計原則

依賴倒置原則:要依賴抽象,不要依賴具體類。聽起來像是針對介面程式設計,不針對實現程式設計,但是這個原則說明了:不能讓高層元件依賴底層元件,而且,不管高層或底層元件,兩者都應該依賴於抽象。例如,下圖中 PizzaStore 屬於高層元件,但是它依賴於底層元件 Pizza 的具體類。如果依賴的是 Pizza 的抽象類,那麼就可以不用關心 Pizza 的具體實現細節。

2. 模式定義

提供一個介面,用於建立相關或依賴物件的家族,而不需要明確指定具體類。

3. 模式類圖

抽象工廠模式建立的是物件家族,也就是很多物件而不是一個物件,並且這些物件是相關的,也就是說必須一起創建出來。而工廠模式只是用於建立一個物件,這和抽象工廠模式有很大不同。並且,抽象工廠模式也用到了工廠模式來建立單一物件,在類圖左部,AbstractFactory 中的 CreateProductA 和 CreateProductB 方法都是讓子類來實現,這兩個方法單獨來看就是在建立一個物件,這符合工廠模式的定義。至於建立物件的家族這一概念是在 Client 體現,Client 要通過 AbstractFactory 同時呼叫兩個方法來創建出兩個物件,在這裡這兩個物件就有很大的相關性,Client 需要這兩個物件的協作才能完成任務。從高層次來看,抽象工廠使用了組合,即 Cilent 組合了 AbstractFactory ,而工廠模式使用了繼承。

4. 解決方案類圖

5. 程式碼實現

public interface Dough {
    public String doughType();
}
public class ThickCrustDough implements Dough{

    @Override
    public String doughType() {
        return "ThickCrustDough";
    }
}
public class ThinCrustDough implements Dough {
    @Override
    public String doughType() {
        return "ThinCrustDough";
    }
}
public interface Sauce {
    public String sauceType();
}
public class MarinaraSauce implements Sauce {
    @Override
    public String sauceType() {
        return "MarinaraSauce";
    }
}
public class PlumTomatoSauce implements Sauce {
    @Override
    public String sauceType() {
        return "PlumTomatoSauce";
    }
}
public interface PizzaIngredientFactory {
    public Dough createDough();
    public Sauce createSauce();
}
public class NYPizzaIngredientFactory implements PizzaIngredientFactory{
    @Override
    public Dough createDough() {
        return new ThickCrustDough();
    }

    @Override
    public Sauce createSauce() {
        return new MarinaraSauce();
    }
}
public class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory{
    @Override
    public Dough createDough() {
        return new ThinCrustDough();
    }

    @Override
    public Sauce createSauce() {
        return new PlumTomatoSauce();
    }
}
public class NYPizzaStore {
    private PizzaIngredientFactory ingredientFactory;

    public NYPizzaStore() {
        ingredientFactory = new NYPizzaIngredientFactory();
    }

    public void makePizza() {
        Dough dough = ingredientFactory.createDough();
        Sauce sauce = ingredientFactory.createSauce();
        System.out.println(dough.doughType());
        System.out.println(sauce.sauceType());
    }
}
public class NYPizzaStoreTestDrive {
    public static void main(String[] args) {
        NYPizzaStore nyPizzaStore = new NYPizzaStore();
        nyPizzaStore.makePizza();
    }
}

執行結果

ThickCrustDough
MarinaraSauce

第 5 章 單件模式

1. 模式定義

確保一個類只有一個例項,並提供了一個全域性訪問點。

2. 模式類圖

單件模式的 Java 實現用一個私有構造器、一個私有靜態變數以及一個公有靜態函式,該函式返回私有變數,使得所有通過該函式獲取的物件都指向這個唯一的私有靜態變數。

3. 經典實現

以下實現中,私有靜態變數被延遲化例項化,這樣做的好處是,如果沒有用到該類,那麼就不會建立該私有靜態變數,從而節約資源。這個實現在多執行緒環境下是不安全的,因為多個執行緒能夠同時進入 if(uniqueInstance == null) 內的語句塊,那麼就會多次例項化 uniqueInstance 私有靜態變數。

public class Singleton {

    private static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

4. 執行緒不安全問題的解決方案一

只需要對 getUniqueInstance() 方法加鎖,就能讓該方法一次只能一個執行緒訪問,從而避免了對 uniqueInstance 變數進行多次例項化的問題。但是這樣有一個問題是一次只能一個執行緒進入,效能上會有一定的浪費。

    public static synchronized Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }

5. 執行緒不安全問題的解決方案二

不用延遲例項化,採用直接例項化。

private static Singleton uniqueInstance = new Singleton();

6. 執行緒不安全問題的解決方案三

考慮第一個解決方案,它是直接對 getUniqueInstance() 方法進行加鎖,而實際上只需要對 uniqueInstance = new Singleton(); 這條語句加鎖即可。使用兩個條件語句來判斷 uniqueInstance 是否已經例項化,如果沒有例項化才需要加鎖。

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public static synchronized Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

第 6 章 命令模式

1. 問題描述

設計一個遙控器,它有很多按鈕,每個按鈕可以發起一個命令,讓一個家電完成相應操作。有非常多的家電,並且也會增加家電。

2. 模式定義

將命令封裝成物件,以便使用不同的命令來引數化其它物件。

3. 模式類圖

4. 解決方案類圖

Invoker 是遙控器,它可以設定命令,並且呼叫命令物件的 execute() 方法。Receiver 是電燈,是命令真正的執行者。ConcreteCommand 類組合了一個 Receiver 物件,命令的執行委託給 Receiver 物件來處理,也就是 LightOnCommand 命令的 excute 方法委託給 Light 物件來處理,Light 物件通過呼叫 on() 方法來完成操作。Invoker 不是 Client 物件,是因為命令的建立不是在 Invoker 中完成的,因此需要額外的 Client 物件來處理這些操作。

5. 程式碼實現

public interface Command {
    public void execute();
}
public class Light {

    public void on() {
        System.out.println("Light is on!");
    }

    public void off() {
        System.out.println("Light is off!");
    }
}
public class LightOnCommand implements Command{
    Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.on();
    }
}
/**
 * 遙控器類
 */
public class SimpleRemoteControl {
    Command slot;

    public SimpleRemoteControl() {

    }

    public void setCommand(Command command) {
        this.slot = command;
    }

    public void buttonWasPressed() {
        slot.execute();
    }

}
public class RemoteLoader {
    public static void main(String[] args) {
        SimpleRemoteControl remote = new SimpleRemoteControl();
        Light light = new Light();
        LightOnCommand lightOnCommand = new LightOnCommand(light);
        remote.setCommand(lightOnCommand);
        remote.buttonWasPressed();
    }
}

輸出

Light is on!

第 7 章 介面卡模式與外觀模式

7.1 介面卡模式

1. 模式定義

將一個類的介面,轉換為客戶期望的另一個介面。介面卡讓原本不相容的類可以合作無間。

2. 模式類圖

有兩種介面卡模式的實現,一種是物件方式,一種是類方式。物件方式是通過組合的方法,讓介面卡類(Adapter)擁有一個待適配的物件(Adaptee),從而把相應的處理委託給待適配的物件。類方式用到多重繼承,Adapter 可以看成 Target 和 Adaptee 型別,先把它當成 Adaptee 型別然後例項化一個 Adapter 物件,再把它當成 Target 型別的,這樣 Client 就可以把這個物件當成 Target 的物件來處理。

3. 問題描述

讓鴨子(Duck)適配火雞(Turkey),Duck 有 quack() 方法,而 Turkey 只有 gobble() 方法。也就是要讓 Turkey 也有 Duck 的 quack() 方法。

4. 解決方案類圖

5. 程式碼實現

public interface Duck {
    public void quack();
    public void fly();
}
public interface Turkey {
    public void gobble();
    public void