1. 程式人生 > 其它 >設計模式-建立型模式

設計模式-建立型模式

目錄

閱讀推薦:

專案地址:https://gitee.com/zwtgit/gof23

學習網站推薦:

設計模式是針對軟體設計中常見問題的工具箱, 其中的工具就是各種經過實踐驗證的解決方案。 即使你從未遇到過這些問題, 瞭解模式仍然非常有用, 因為它能指導你如何使用面向物件的設計原則來解決各種問題。

演算法更像是菜譜: 提供達成目標的明確步驟。 而模式更像是藍圖: 你可以看到最終的結果和模式的功能, 但需要自己確定實現步驟。

GoF 23(分類)

  • 建立型模式:提供建立物件的機制, 增加已有程式碼的靈活性和可複用性。
  • 結構型模式:介紹如何將物件和類組裝成較大的結構, 並同時保持結構的靈活和高效。
  • 行為模式:負責物件間的高效溝通和職責委派。
不同設計模式的複雜程度、 細節層次以及在整個系統中的應用範圍等方面各不相同。 
我喜歡將其類比於道路的建造: 
如果你希望讓十字路口更加安全, 那麼可以安裝一些交通訊號燈, 或者修建包含行人地下通道在內的多層互通式立交橋。

最基礎的、 底層的模式通常被稱為慣用技巧。 這類模式一般只能在一種程式語言中使用。

最通用的、 高層的模式是構架模式。 開發者可以在任何程式語言中使用這類模式。 
與其他模式不同, 它們可用於整個應用程式的架構設計。
  • 建立型模式:
    • 單例模式,工廠模式, 抽象工廠模式, 建造者模式, 原型模式
  • 結構型模式:
    • 介面卡模式, 橋接模式, 裝飾模式, 組合模式, 外觀模式, 享元模式, 代理模式
  • 行為型模式:
    • 模板方法模式, 命令模式, 迭代器模式, 觀察者模式, 中介者模式, 備忘錄模式, 直譯器模式, 狀態模式, 策略模式, 職責鏈模式, 訪問者模式

OOP 七大原則

面向物件程式設計(Object Oriented Programming,OOP)。

1、開閉原則

對擴充套件開放, 對修改關閉。

2、單一職責原則
每個類應該實現單一的職責,不要存在多於一個導致類變更的原因,否則就應該把類拆分。該原則是實現高內聚、低耦合的指導方針。

3、里氏替換原則(Liskov Substitution Principle)
任何基類可以出現的地方,子類一定可以出現。里氏替換原則是繼承複用的基石,只有當衍生類可以替換基類,軟體單位的功能不受到影響時,基類才能真正被複用,而衍生類也能夠在基類的基礎上增加新的行為。

里氏替換原則是對開閉原則的補充。實現開閉原則的關鍵就是抽象化。而基類與子類的繼承關係就是抽象化的具體實現,所以里氏替換原則是對實現抽象化的具體步驟的規範。里氏替換原則中,子類對父類的方法儘量不要重寫和過載。因為父類代表了定義好的結構,通過這個規範的介面與外界互動,子類不應該隨便破壞它。

4、依賴倒轉原則(Dependence Inversion Principle)
面向介面程式設計,依賴於抽象而不依賴於具體。用到具體類時,不與具體類互動,而與具體類的上層介面互動。

5、介面隔離原則(Interface Segregation Principle)
每個介面中不存在子類用不到卻必須實現的方法,否則就要將介面拆分。使用多個隔離的介面,比使用單個介面(多個介面中的方法聚合到一個的介面)要好。

6、迪米特法則(最少知道原則)(Demeter Principle)
一個類對自己依賴的類知道的越少越好。無論被依賴的類多麼複雜,都應該將邏輯封裝在方法的內部,通過 public 方法提供給外部。這樣當被依賴的類變化時,才能最小的影響該類。

7、合成複用原則(Composite Reuse Principle)
軟體複用時,要先儘量使用組合或者聚合等關聯關係實現,其次才考慮使用繼承。即在一個新物件裡通過關聯的方式使用已有物件的一些方法和功能。

Creational Pattems

Singleton

背景

保證一個類只有一個例項, 並提供一個訪問該例項的全域性節點。

政府是單例模式的一個很好的示例。 一個國家只有一個官方政府。 不管組成政府的每個人的身份是什麼, “某政府” 這一稱謂總是鑑別那些掌權者的全域性訪問節點。

適用場景

  • 如果程式中的某個類對於所有客戶端只有一個可用的例項,可以使用單例模式。
  • 如果你需要更加嚴格地控制全域性變數,可以使用單例模式 。

例項-實現

Java 核心程式庫中仍有相當多的單例示例:

基礎單例(單執行緒)

package com.zwt.BasicSingleton;
/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-09
 * Blog: https://www.cnblogs.com/zwtblog/
 */
public final class Singleton {
    private static Singleton instance;
    public String value;

    private Singleton(String value) {
        // The following code emulates slow initialization.
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        this.value = value;
    }

    public static Singleton getInstance(String value) {
        if (instance == null) {
            instance = new Singleton(value);
        }
        return instance;
    }
}

package com.zwt.BasicSingleton;
/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-09
 * Blog: https://www.cnblogs.com/zwtblog/
 */
public class DemoSingleThread {
    public static void main(String[] args) {
        System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" +
                "If you see different values, then 2 singletons were created (booo!!)" + "\n\n" +
                "RESULT:" + "\n");

        Singleton anotherSingleton = Singleton.getInstance("wb");
        Singleton singleton = Singleton.getInstance("zwt");

        System.out.println(singleton.value);
        System.out.println(anotherSingleton.value);
    }
}

If you see the same value, then singleton was reused (yay!)
If you see different values, then 2 singletons were created (booo!!)

RESULT:

wb
wb

基礎單例(多執行緒)

相同的類在多執行緒環境中會出錯。 多執行緒可能會同時呼叫構建方法並獲取多個單例類的例項。

package com.zwt.BasicSingleton;
/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-09
 * Blog: https://www.cnblogs.com/zwtblog/
 */
public class DemoMultiThread {
    public static void main(String[] args) {
        System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" +
                "If you see different values, then 2 singletons were created (booo!!)" + "\n\n" +
                "RESULT:" + "\n");

        Thread threadFoo = new Thread(new Threadwb());
        Thread threadBar = new Thread(new Threadzwt());
        threadFoo.start();
        threadBar.start();

    }

    static class Threadwb implements Runnable {
        @Override
        public void run() {
            Singleton singleton = Singleton.getInstance("wb");
            System.out.println(singleton.value);
        }
    }

    static class Threadzwt implements Runnable {
        @Override
        public void run() {
            Singleton singleton = Singleton.getInstance("zwt");
            System.out.println(singleton.value);
        }
    }
}

If you see the same value, then singleton was reused (yay!)
If you see different values, then 2 singletons were created (booo!!)

RESULT:

wb
zwt

採用延遲載入的執行緒安全單例

為了解決這個問題, 你必須在建立首個單例物件時對執行緒進行同步。

package com.zwt.Singleton;
/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-09
 * Blog: https://www.cnblogs.com/zwtblog/
 */
public final class Singleton {
    private static volatile Singleton instance;

    public String value;

    private Singleton(String value) {
        this.value = value;
    }

    public static Singleton getInstance(String value) {
        Singleton result = instance;
        if (result != null) {
            return result;
        }
        synchronized(Singleton.class) {
            if (instance == null) {
                instance = new Singleton(value);
            }
            return instance;
        }
    }
}
package com.zwt.Singleton;
/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-09
 * Blog: https://www.cnblogs.com/zwtblog/
 */
public class DemoMultiThread {
    public static void main(String[] args) {
        System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" +
                "If you see different values, then 2 singletons were created (booo!!)" + "\n\n" +
                "RESULT:" + "\n");
        Thread threadFoo = new Thread(new ThreadFoo());
        Thread threadBar = new Thread(new ThreadBar());
        threadFoo.start();
        threadBar.start();
    }

    static class ThreadFoo implements Runnable {
        @Override
        public void run() {
            Singleton singleton = Singleton.getInstance("FOO");
            System.out.println(singleton.value);
        }
    }

    static class ThreadBar implements Runnable {
        @Override
        public void run() {
            Singleton singleton = Singleton.getInstance("BAR");
            System.out.println(singleton.value);
        }
    }
}
If you see the same value, then singleton was reused (yay!)
If you see different values, then 2 singletons were created (booo!!)

RESULT:

FOO
FOO

理論-實現

Eager initialization

在預先初始化中,單例類的例項是在類載入時建立的,這是建立單例類的最簡單方法,但它有一個缺點,即即使客戶端應用程式可能不會使用它,也會建立例項。

如果您的單例類沒有使用大量資源,則可以使用這種方法。但是在大多數情況下,Singleton 類是為檔案系統、資料庫連線等資源建立的。除非客戶端呼叫該getInstance方法,否則我們應該避免例項化。此外,此方法不提供任何異常處理選項。

public class EagerInitializedSingleton {
    
    private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
    
    //private constructor to avoid client applications to use constructor
    private EagerInitializedSingleton(){}

    public static EagerInitializedSingleton getInstance(){
        return instance;
    }
}

Static block initialization

public class StaticBlockSingleton {

    private static StaticBlockSingleton instance;
    
    private StaticBlockSingleton(){}
    
    //static block initialization for exception handling
    static{
        try{
            instance = new StaticBlockSingleton();
        }catch(Exception e){
            throw new RuntimeException("Exception occured in creating singleton instance");
        }
    }
    
    public static StaticBlockSingleton getInstance(){
        return instance;
    }
}

Lazy Initialization

在單執行緒環境下工作正常,但是當涉及到多執行緒系統時,如果多個執行緒同時處於 if 條件內,則可能會導致問題。它將破壞單例模式,兩個執行緒將獲得單例類的不同例項。

public class LazyInitializedSingleton {

    private static LazyInitializedSingleton instance;
    
    private LazyInitializedSingleton(){}
    
    public static LazyInitializedSingleton getInstance(){
        if(instance == null){
            instance = new LazyInitializedSingleton();
        }
        return instance;
    }
}

Thread Safe Singleton

public class ThreadSafeSingleton {

    private static ThreadSafeSingleton instance;
    
    private ThreadSafeSingleton(){}
    
    public static synchronized ThreadSafeSingleton getInstance(){
        if(instance == null){
            instance = new ThreadSafeSingleton();
        }
        return instance;
    }
    
}

但由於與同步方法相關的成本,它降低了效能,儘管我們只需要它用於可能建立單獨例項的前幾個執行緒(閱讀:Java 同步)。為了每次都避免這種額外的開銷,使用了雙重檢查鎖定原則。在這種方法中,同步塊在 if 條件中使用,並進行額外檢查以確保僅建立單例類的一個例項。


public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){
    if(instance == null){
        synchronized (ThreadSafeSingleton.class) {
            if(instance == null){
                instance = new ThreadSafeSingleton();
            }
        }
    }
    return instance;
}

Bill Pugh Singleton Implementation

在 Java 5 之前,java 記憶體模型有很多問題,並且在某些場景中,如果太多執行緒試圖同時獲取 Singleton 類的例項,上述方法會失敗。

請注意包含單例類例項的私有內部靜態類。當載入單例類時,SingletonHelper類不會載入到記憶體中,只有當有人呼叫getInstance方法時,才會載入這個類並建立單例類例項。

這是 Singleton 類最廣泛使用的方法,因為它不需要同步。我在我的許多專案中都使用了這種方法,而且它也很容易理解和實現。

public class BillPughSingleton {

    private BillPughSingleton(){}
    
    private static class SingletonHelper{
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }
    
    public static BillPughSingleton getInstance(){
        return SingletonHelper.INSTANCE;
    }
}

Using Reflection to destroy Singleton Pattern

反射可用於破壞上述所有單例實現方法。讓我們用一個示例類來看看這個。

您執行上面的測試類時,您會注意到兩個例項的 hashCode 不相同,這破壞了單例模式。反射非常強大,並在很多框架中使用,如 Spring 和 Hibernate。

import java.lang.reflect.Constructor;
/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-09
 * Blog: https://www.cnblogs.com/zwtblog/
 */
public class ReflectionSingletonTest {

    public static void main(String[] args) {

        EagerInitializedSingleton instanceOne = EagerInitializedSingleton.getInstance();
        EagerInitializedSingleton instanceTwo = null;
        EagerInitializedSingleton instanceThree = null;

        try {
            Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();

            for (Constructor constructor : constructors) {
                //Below code will destroy the singleton pattern
                //關閉了檢查
                constructor.setAccessible(true);
                instanceTwo = (EagerInitializedSingleton) constructor.newInstance();
                instanceThree = (EagerInitializedSingleton) constructor.newInstance();
                break;
            }

        } catch (Exception e) {
            e.printStackTrace();
        }


        System.out.println(instanceOne.hashCode());
        System.out.println(instanceTwo.hashCode());
        System.out.println(instanceThree.hashCode());

    }

}

Enum Singleton

public enum EnumSingleton {

    INSTANCE;
    
    public static void doSomething(){
        //do something
    }
}

Serialization and Singleton

有時在分散式系統中,我們需要在 Singleton 類中實現 Serializable 介面,以便我們可以將其狀態儲存在檔案系統中並在以後的某個時間點檢索它。這是一個也實現了 Serializable 介面的小型單例類。

package com.zwt.SerializableSingleton;

import java.io.Serializable;

/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-09
 * Blog: https://www.cnblogs.com/zwtblog/
 */
public class SerializedSingleton implements Serializable {

    private static final long serialVersionUID = -7604766932017737115L;

    private SerializedSingleton() {
    }

    private static class SingletonHelper {
        private static final SerializedSingleton instance = new SerializedSingleton();
    }

    public static SerializedSingleton getInstance() {
        return SingletonHelper.instance;
    }

    //這樣當JVM從記憶體中反序列化地"組裝"一個新物件時
    //就會自動呼叫這個 readResolve方法來返回我們指定好的物件了,
    protected Object readResolve() {
        return getInstance();
    }


}

序列化單例類的問題在於,無論何時反序列化它,它都會建立該類的一個新例項。讓我們用一個簡單的程式來看看它。

package com.zwt.SerializableSingleton;

import java.io.*;

/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-09
 * Blog: https://www.cnblogs.com/zwtblog/
 */
public class SingletonSerializedTest {

    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {

        SerializedSingleton instanceOne = SerializedSingleton.getInstance();

        ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
                "filename.ser"));
        out.writeObject(instanceOne);
        out.close();

        //deserailize from file to object
        ObjectInput in = new ObjectInputStream(new FileInputStream(
                "filename.ser"));
        SerializedSingleton instanceTwo = (SerializedSingleton) in.readObject();
        in.close();

        System.out.println("instanceOne hashCode=" + instanceOne.hashCode());
        System.out.println("instanceTwo hashCode=" + instanceTwo.hashCode());

        System.out.println("===============");


    }

}

所以它破壞了單例模式,為了克服這種情況,我們需要做的就是提供readResolve()方法的實現。

protected Object readResolve() {
    return getInstance();
}

與其他模式關係

  • 外觀模式類通常可以轉換為單例模式類, 因為在大部分情況下一個外觀物件就足夠了。
  • 如果你能將物件的所有共享狀態簡化為一個享元物件, 那麼享元模式就和單例類似了。 但這兩個模式有兩個根本性的不同。
    1. 只會有一個單例實體, 但是享元類可以有多個實體, 各實體的內在狀態也可以不同。
    2. 單例物件可以是可變的。 享元物件是不可變的。
  • 抽象工廠模式生成器模式原型模式都可以用單例來實現。

Factory

背景

工廠方法模式是一種建立型設計模式, 其在父類中提供一個建立物件的方法, 允許子類決定例項化物件的型別。

假設你正在開發一款物流管理應用。 最初版本只能處理卡車運輸, 因此大部分程式碼都在位於名為 卡車的類中。

一段時間後, 這款應用變得極受歡迎。 你每天都能收到十幾次來自海運公司的請求, 希望應用能夠支援海上物流功能。

如果程式碼其餘部分與現有類已經存在耦合關係, 那麼向程式中新增新類其實並沒有那麼容易。

工廠方法模式建議使用特殊的工廠方法代替對於物件建構函式的直接呼叫 (即使用 new運算子)。 
不用擔心, 物件仍將通過 new運算子建立, 只是該運算子改在工廠方法中呼叫罷了。 工廠方法返回的物件通常被稱作 “產品”。

乍看之下, 這種更改可能毫無意義: 
我們只是改變了程式中呼叫建構函式的位置而已。 但是, 仔細想一下, 現在你可以在子類中重寫工廠方法, 從而改變其建立產品的型別。

但有一點需要注意:僅當這些產品具有共同的基類或者介面時, 子類才能返回不同型別的產品, 同時基類中的工廠方法還應將其返回型別宣告為這一共有介面。

呼叫工廠方法的程式碼 (通常被稱為客戶端程式碼) 無需瞭解不同子類返回實際物件之間的差別。 客戶端將所有產品視為抽象的 運輸 。 客戶端知道所有運輸物件都提供 交付方法, 但是並不關心其具體實現方式。

適用場景

  • 當你在編寫程式碼的過程中, 如果無法預知物件確切類別及其依賴關係時, 可使用工廠方法。
  • 如果你希望使用者能擴充套件你軟體庫或框架的內部元件, 可使用工廠方法。
  • 如果你希望複用現有物件來節省系統資源, 而不是每次都重新建立物件, 可使用工廠方法。

例項-實現

工廠方法模式在 Java 程式碼中得到了廣泛使用。 當你需要在程式碼中提供高層次的靈活性時, 該模式會非常實用。

  1. java.util.Calendar、ResourceBundle 和 NumberFormatgetInstance()方法使用工廠模式。
  2. valueOf() Boolean、Integer 等包裝類中的方法。

核心 Java 程式庫中有該模式的應用:

識別方法: 工廠方法可通過構建方法來識別, 它會建立具體類的物件, 但以抽象型別或介面的形式返回這些物件。

生成跨平臺的 GUI 元素

buttons

buttons/Button.java: 通用產品介面

package com.zwt.buttons;
/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-10
 * Blog: https://www.cnblogs.com/zwtblog/
 */
/**
 * Common interface for all buttons.
 */
public interface Button {
    void render();

    void onClick();
}

buttons/HtmlButton.java: 具體產品

package com.zwt.buttons;
/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-10
 * Blog: https://www.cnblogs.com/zwtblog/
 */
/**
 * HTML button implementation.
 */
public class HtmlButton implements Button {

    public void render() {
        System.out.println("<button>Test Button</button>");
        onClick();
    }

    public void onClick() {
        System.out.println("Click! Button says - 'Hello World!'");
    }
}

buttons/WindowsButton.java: 另一個具體產品

package com.zwt.buttons;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-10
 * Blog: https://www.cnblogs.com/zwtblog/
 */
/**
 * Windows button implementation.
 */
public class WindowsButton implements Button {
    JPanel panel = new JPanel();
    JFrame frame = new JFrame();
    JButton button;

    public void render() {
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JLabel label = new JLabel("Hello World!");
        label.setOpaque(true);
        label.setBackground(new Color(235, 233, 126));
        label.setFont(new Font("Dialog", Font.BOLD, 44));
        label.setHorizontalAlignment(SwingConstants.CENTER);
        panel.setLayout(new FlowLayout(FlowLayout.CENTER));
        frame.getContentPane().add(panel);
        panel.add(label);
        onClick();
        panel.add(button);

        frame.setSize(320, 200);
        frame.setVisible(true);
        onClick();
    }

    public void onClick() {
        button = new JButton("Exit");
        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                frame.setVisible(false);
                System.exit(0);
            }
        });
    }
}

factory

factory/Dialog.java: 基礎建立者

package com.zwt.factory;

import com.zwt.buttons.Button;

/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-10
 * Blog: https://www.cnblogs.com/zwtblog/
 */

/**
 * Base factory class. Note that "factory" is merely a role for the class. It
 * should have some core business logic which needs different products to be
 * created.
 */
public abstract class Dialog {

    public void renderWindow() {
        // ... other code ...

        Button okButton = createButton();
        okButton.render();
    }

    /**
     * Subclasses will override this method in order to create specific button
     * objects.
     */
    public abstract Button createButton();
}

factory/HtmlDialog.java: 具體建立者

package com.zwt.factory;

/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-10
 * Blog: https://www.cnblogs.com/zwtblog/
 */

import com.zwt.buttons.Button;
import com.zwt.buttons.HtmlButton;

/**
 * HTML Dialog will produce HTML buttons.
 */
public class HtmlDialog extends Dialog {

    @Override
    public Button createButton() {
        return new HtmlButton();
    }
}

factory/WindowsDialog.java: 另一個具體建立者

package com.zwt.factory;

import com.zwt.buttons.Button;
import com.zwt.buttons.WindowsButton;
/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-10
 * Blog: https://www.cnblogs.com/zwtblog/
 */

/**
 * Windows Dialog will produce Windows buttons.
 */
public class WindowsDialog extends Dialog {

    @Override
    public Button createButton() {
        return new WindowsButton();
    }
}

Demo.java: 客戶端程式碼

package com.zwt;
/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-10
 * Blog: https://www.cnblogs.com/zwtblog/
 */

import com.zwt.factory.Dialog;
import com.zwt.factory.HtmlDialog;
import com.zwt.factory.WindowsDialog;

/**
 * Demo class. Everything comes together here.
 */
public class Demo {
    private static Dialog dialog;

    public static void main(String[] args) {
        configure();
        runBusinessLogic();
    }

    /**
     * The concrete factory is usually chosen depending on configuration or
     * environment options.
     */
    static void configure() {
        if (System.getProperty("os.name").equals("Windows 10")) {
            dialog = new WindowsDialog();
        } else {
            dialog = new HtmlDialog();
        }
    }

    /**
     * All of the client code should work with factories and products through
     * abstract interfaces. This way it does not care which factory it works
     * with and what kind of product it returns.
     */
    static void runBusinessLogic() {
        dialog.renderWindow();
    }
}

OutputDemo.txt: 執行結果 (Html­Dialog)

<button>Test Button</button>
Click! Button says - 'Hello World!'

OutputDemo.png: 執行結果 (Windows­Dialog)

與其他模式的關係

Abstract Factory

背景

抽象工廠模式是一種建立型設計模式, 它能建立一系列相關的物件, 而無需指定其具體類。

假設你正在開發一款傢俱商店模擬器。 你的程式碼中包括一些類, 用於表示:

一系列相關產品, 例如 椅子Chair 、 ​ 沙發Sofa 和 咖啡桌Coffee ­Table 。

系列產品的不同變體。 例如, 你可以使用 現代Modern 、 ​ 維多利亞Victorian 、 ​ 裝飾風藝術Art­Deco等風格生成 椅子 、 ​ 沙發 和 咖啡桌 。

適用場景

  • 如果程式碼需要與多個不同系列的相關產品互動, 但是由於無法提前獲取相關資訊, 或者出於對未來擴充套件性的考慮, 你不希望程式碼基於產品的具體類進行構建, 在這種情況下, 你可以使用抽象工廠。
  • 如果你有一個基於一組抽象方法的類, 且其主要功能因此變得不明確, 那麼在這種情況下可以考慮使用抽象工廠模式。

例項-實現

使用示例: 抽象工廠模式在 Java 程式碼中很常見。 許多框架和程式庫會將它作為擴充套件和自定義其標準組件的一種方式。

以下是來自核心 Java 程式庫的一些示例:

識別方法: 我們可以通過方法來識別該模式——其會返回一個工廠物件。 接下來, 工廠將被用於建立特定的子元件。

跨平臺 GUI 元件系列及其建立方式

在本例中, 按鈕和複選框將被作為產品。 它們有兩個變體: macOS 版和 Windows 版。

抽象工廠定義了用於建立按鈕和複選框的介面。 而兩個具體工廠都會返回同一變體的兩個產品。

客戶端程式碼使用抽象介面與工廠和產品進行互動。 同樣的程式碼能與依賴於不同工廠物件型別的多種產品變體進行互動。

buttons: 第一個產品層次結構

buttons/Button.java

package com.zwt.buttons;

/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-10
 * Blog: https://www.cnblogs.com/zwtblog/
 */

/**
 * Abstract Factory assumes that you have several families of products,
 * structured into separate class hierarchies (Button/Checkbox). All products of
 * the same family have the common interface.
 * <p>
 * This is the common interface for buttons family.
 */
public interface Button {
    void paint();
}

buttons/MacOSButton.java

package com.zwt.buttons;

/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-10
 * Blog: https://www.cnblogs.com/zwtblog/
 */

/**
 * All products families have the same varieties (MacOS/Windows).
 * <p>
 * This is a MacOS variant of a button.
 */
public class MacOSButton implements Button {

    @Override
    public void paint() {
        System.out.println("You have created MacOSButton.");
    }
}

buttons/WindowsButton.java

package refactoring_guru.abstract_factory.example.buttons;

package com.zwt.buttons;

/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-10
 * Blog: https://www.cnblogs.com/zwtblog/
 */

/**
 * All products families have the same varieties (MacOS/Windows).
 * <p>
 * This is another variant of a button.
 */
public class WindowsButton implements Button {

    @Override
    public void paint() {
        System.out.println("You have created WindowsButton.");
    }
}

checkboxes: 第二個產品層次結構

checkboxes/Checkbox.java

package com.zwt.checkboxes;

/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-10
 * Blog: https://www.cnblogs.com/zwtblog/
 */

/**
 * Checkboxes is the second product family. It has the same variants as buttons.
 */
public interface Checkbox {
    void paint();
}

checkboxes/MacOSCheckbox.java

/**
 * All products families have the same varieties (MacOS/Windows).
 *
 * This is a variant of a checkbox.
 */
public class MacOSCheckbox implements Checkbox {

    @Override
    public void paint() {
        System.out.println("You have created MacOSCheckbox.");
    }
}

checkboxes/WindowsCheckbox.java

/**
 * All products families have the same varieties (MacOS/Windows).
 *
 * This is another variant of a checkbox.
 */
public class WindowsCheckbox implements Checkbox {

    @Override
    public void paint() {
        System.out.println("You have created WindowsCheckbox.");
    }
}

factories

factories/GUIFactory.java: 抽象工廠

package com.zwt.factories;

/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-10
 * Blog: https://www.cnblogs.com/zwtblog/
 */

import com.zwt.buttons.Button;
import com.zwt.checkboxes.Checkbox;

/**
 * Abstract factory knows about all (abstract) product types.
 */
public interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

factories/MacOSFactory.java: 具體工廠 ( mac­OS)

package com.zwt.factories;

/**
 * @author ML李嘉圖
 * @version createtime: 2021-11-10
 * Blog: https://www.cnblogs.com/zwtblog/
 */

import com.zwt.buttons.Button;
import com.zwt.buttons.MacOSButton;
import com.zwt.checkboxes.Checkbox;
import com.zwt.checkboxes.MacOSCheckbox;

/**
 * Each concrete factory extends basic factory and responsible for creating
 * products of a single variety.
 */
public class MacOSFactory implements GUIFactory {

    @Override
    public Button createButton() {
        return new MacOSButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new MacOSCheckbox();
    }
}

factories/WindowsFactory.java: 具體工廠 (Windows)

package com.zwt.factories;

import com.zwt.buttons.Button;
import com.zwt.buttons.WindowsButton;
import com.zwt.checkboxes.Checkbox;
import com.zwt.checkboxes.WindowsCheckbox;

/**
 * Each concrete factory extends basic factory and responsible for creating
 * products of a single variety.
 */
public class WindowsFactory implements GUIFactory {

    @Override
    public Button createButton() {
        return new WindowsButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new WindowsCheckbox();
    }
}

app

app/Application.java: 客戶端程式碼

package com.zwt.app;

import com.zwt.buttons.Button;
import com.zwt.checkboxes.Checkbox;
import com.zwt.factories.GUIFactory;

/**
 * Factory users don't care which concrete factory they use since they work with
 * factories and products through abstract interfaces.
 */
public class Application {
    private Button button;
    private Checkbox checkbox;

    public Application(GUIFactory factory) {
        button = factory.createButton();
        checkbox = factory.createCheckbox();
    }

    public void paint() {
        button.paint();
        checkbox.paint();
    }
}

Demo.java: 程式配置

package com.zwt;

import com.zwt.app.Application;
import com.zwt.factories.GUIFactory;
import com.zwt.factories.MacOSFactory;
import com.zwt.factories.WindowsFactory;

/**
 * Demo class. Everything comes together here.
 */
public class Demo {

    /**
     * Application picks the factory type and creates it in run time (usually at
     * initialization stage), depending on the configuration or environment
     * variables.
     */
    private static Application configureApplication() {
        Application app;
        GUIFactory factory;
        String osName = System.getProperty("os.name").toLowerCase();
        if (osName.contains("mac")) {
            factory = new MacOSFactory();
            app = new Application(factory);
        } else {
            factory = new WindowsFactory();
            app = new Application(factory);
        }
        return app;
    }

    public static void main(String[] args) {
        Application app = configureApplication();
        app.paint();
    }
}

與其他模式的關係

Builder

亦稱:建造者模式、Builder

背景

假設有這樣一個複雜物件, 在對其進行構造時需要對諸多成員變數和巢狀物件進行繁複的初始化工作。 
這些初始化程式碼通常深藏於一個包含眾多引數且讓人基本看不懂的建構函式中; 
甚至還有更糟糕的情況, 那就是這些程式碼散落在客戶端程式碼的多個位置。

生成器模式建議將物件構造程式碼從產品類中抽取出來, 並將其放在一個名為生成器的獨立物件中。

適用場景

  • 使用生成器模式可避免 “重疊建構函式 (telescopic constructor)” 的出現。
  • 當你希望使用程式碼建立不同形式的產品 (例如石頭或木頭房屋) 時, 可使用生成器模式。
  • 使用生成器構造組合樹或其他複雜物件。

例項-實現

生成器在 Java 核心程式庫中得到了廣泛的應用:

識別方法: 生成器模式可以通過類來識別, 它擁有一個構建方法和多個配置結果物件的方法。 生成器方法通常支援鏈式程式設計 (例如 someBuilder->setValueA(1)->setValueB(2)->create() )。

分步製造汽車

在本例中, 生成器模式允許你分步驟地製造不同型號的汽車。

示例還展示了生成器如何使用相同的生產過程製造不同型別的產品 (汽車手冊)。

主管控制著構造順序。 它知道製造各種汽車型號需要呼叫的生產步驟。 它僅與汽車的通用介面進行互動。 這樣就能將不同型別的生成器傳遞給主管了。

最終結果將從生成器物件中獲得, 因為主管不知道最終產品的型別。 只有生成器物件知道自己生成的產品是什麼。

builders

builders/Builder.java: 通用生成器介面

/**
 * Builder interface defines all possible ways to configure a product.
 */
public interface Builder {
    void setCarType(CarType type);
    void setSeats(int seats);
    void setEngine(Engine engine);
    void setTransmission(Transmission transmission);
    void setTripComputer(TripComputer tripComputer);
    void setGPSNavigator(GPSNavigator gpsNavigator);
}

builders/CarBuilder.java: 汽車生成器

/**
 * Concrete builders implement steps defined in the common interface.
 */
public class CarBuilder implements Builder {
    private CarType type;
    private int seats;
    private Engine engine;
    private Transmission transmission;
    private TripComputer tripComputer;
    private GPSNavigator gpsNavigator;

    public void setCarType(CarType type) {
        this.type = type;
    }

    @Override
    public void setSeats(int seats) {
        this.seats = seats;
    }

    @Override
    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    @Override
    public void setTransmission(Transmission transmission) {
        this.transmission = transmission;
    }

    @Override
    public void setTripComputer(TripComputer tripComputer) {
        this.tripComputer = tripComputer;
    }

    @Override
    public void setGPSNavigator(GPSNavigator gpsNavigator) {
        this.gpsNavigator = gpsNavigator;
    }

    public Car getResult() {
        return new Car(type, seats, engine, transmission, tripComputer, gpsNavigator);
    }
}

builders/CarManualBuilder.java: 汽車手冊生成器

/**
 * Unlike other creational patterns, Builder can construct unrelated products,
 * which don't have the common interface.
 *
 * In this case we build a user manual for a car, using the same steps as we
 * built a car. This allows to produce manuals for specific car models,
 * configured with different features.
 */
public class CarManualBuilder implements Builder{
    private CarType type;
    private int seats;
    private Engine engine;
    private Transmission transmission;
    private TripComputer tripComputer;
    private GPSNavigator gpsNavigator;

    @Override
    public void setCarType(CarType type) {
        this.type = type;
    }

    @Override
    public void setSeats(int seats) {
        this.seats = seats;
    }

    @Override
    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    @Override
    public void setTransmission(Transmission transmission) {
        this.transmission = transmission;
    }

    @Override
    public void setTripComputer(TripComputer tripComputer) {
        this.tripComputer = tripComputer;
    }

    @Override
    public void setGPSNavigator(GPSNavigator gpsNavigator) {
        this.gpsNavigator = gpsNavigator;
    }

    public Manual getResult() {
        return new Manual(type, seats, engine, transmission, tripComputer, gpsNavigator);
    }
}

cars

cars/Car.java: 汽車產品

/**
 * Car is a product class.
 */
public class Car {
    private final CarType carType;
    private final int seats;
    private final Engine engine;
    private final Transmission transmission;
    private final TripComputer tripComputer;
    private final GPSNavigator gpsNavigator;
    private double fuel = 0;

    public Car(CarType carType, int seats, Engine engine, Transmission transmission,
               TripComputer tripComputer, GPSNavigator gpsNavigator) {
        this.carType = carType;
        this.seats = seats;
        this.engine = engine;
        this.transmission = transmission;
        this.tripComputer = tripComputer;
        if (this.tripComputer != null) {
            this.tripComputer.setCar(this);
        }
        this.gpsNavigator = gpsNavigator;
    }

    public CarType getCarType() {
        return carType;
    }

    public double getFuel() {
        return fuel;
    }

    public void setFuel(double fuel) {
        this.fuel = fuel;
    }

    public int getSeats() {
        return seats;
    }

    public Engine getEngine() {
        return engine;
    }

    public Transmission getTransmission() {
        return transmission;
    }

    public TripComputer getTripComputer() {
        return tripComputer;
    }

    public GPSNavigator getGpsNavigator() {
        return gpsNavigator;
    }
}

cars/Manual.java: 手冊產品

/**
 * Car manual is another product. Note that it does not have the same ancestor
 * as a Car. They are not related.
 */
public class Manual {
    private final CarType carType;
    private final int seats;
    private final Engine engine;
    private final Transmission transmission;
    private final TripComputer tripComputer;
    private final GPSNavigator gpsNavigator;

    public Manual(CarType carType, int seats, Engine engine, Transmission transmission,
                  TripComputer tripComputer, GPSNavigator gpsNavigator) {
        this.carType = carType;
        this.seats = seats;
        this.engine = engine;
        this.transmission = transmission;
        this.tripComputer = tripComputer;
        this.gpsNavigator = gpsNavigator;
    }

    public String print() {
        String info = "";
        info += "Type of car: " + carType + "\n";
        info += "Count of seats: " + seats + "\n";
        info += "Engine: volume - " + engine.getVolume() + "; mileage - " + engine.getMileage() + "\n";
        info += "Transmission: " + transmission + "\n";
        if (this.tripComputer != null) {
            info += "Trip Computer: Functional" + "\n";
        } else {
            info += "Trip Computer: N/A" + "\n";
        }
        if (this.gpsNavigator != null) {
            info += "GPS Navigator: Functional" + "\n";
        } else {
            info += "GPS Navigator: N/A" + "\n";
        }
        return info;
    }
}

cars/CarType.java

public enum CarType {
    CITY_CAR, SPORTS_CAR, SUV
}

components

components/Engine.java: 產品特徵 1

/**
 * Just another feature of a car.
 */
public class Engine {
    private final double volume;
    private double mileage;
    private boolean started;

    public Engine(double volume, double mileage) {
        this.volume = volume;
        this.mileage = mileage;
    }

    public void on() {
        started = true;
    }

    public void off() {
        started = false;
    }

    public boolean isStarted() {
        return started;
    }

    public void go(double mileage) {
        if (started) {
            this.mileage += mileage;
        } else {
            System.err.println("Cannot go(), you must start engine first!");
        }
    }

    public double getVolume() {
        return volume;
    }

    public double getMileage() {
        return mileage;
    }
}

components/GPSNavigator.java: 產品特徵 2

/**
 * Just another feature of a car.
 */
public class GPSNavigator {
    private String route;

    public GPSNavigator() {
        this.route = "221b, Baker Street, London  to Scotland Yard, 8-10 Broadway, London";
    }

    public GPSNavigator(String manualRoute) {
        this.route = manualRoute;
    }

    public String getRoute() {
        return route;
    }
}

components/Transmission.java: 產品特徵 3

/**
 * Just another feature of a car.
 */
public enum Transmission {
    SINGLE_SPEED, MANUAL, AUTOMATIC, SEMI_AUTOMATIC
}

components/TripComputer.java: 產品特徵 4

/**
 * Just another feature of a car.
 */
public class TripComputer {

    private Car car;

    public void setCar(Car car) {
        this.car = car;
    }

    public void showFuelLevel() {
        System.out.println("Fuel level: " + car.getFuel());
    }

    public void showStatus() {
        if (this.car.getEngine().isStarted()) {
            System.out.println("Car is started");
        } else {
            System.out.println("Car isn't started");
        }
    }
}

director

director/Director.java: 主管控制生成器

/**
 * Director defines the order of building steps. It works with a builder object
 * through common Builder interface. Therefore it may not know what product is
 * being built.
 */
public class Director {

    public void constructSportsCar(Builder builder) {
        builder.setCarType(CarType.SPORTS_CAR);
        builder.setSeats(2);
        builder.setEngine(new Engine(3.0, 0));
        builder.setTransmission(Transmission.SEMI_AUTOMATIC);
        builder.setTripComputer(new TripComputer());
        builder.setGPSNavigator(new GPSNavigator());
    }

    public void constructCityCar(Builder builder) {
        builder.setCarType(CarType.CITY_CAR);
        builder.setSeats(2);
        builder.setEngine(new Engine(1.2, 0));
        builder.setTransmission(Transmission.AUTOMATIC);
        builder.setTripComputer(new TripComputer());
        builder.setGPSNavigator(new GPSNavigator());
    }

    public void constructSUV(Builder builder) {
        builder.setCarType(CarType.SUV);
        builder.setSeats(4);
        builder.setEngine(new Engine(2.5, 0));
        builder.setTransmission(Transmission.MANUAL);
        builder.setGPSNavigator(new GPSNavigator());
    }
}

Demo.java: 客戶端程式碼

/**
 * Demo class. Everything comes together here.
 */
public class Demo {

    public static void main(String[] args) {
        Director director = new Director();

        // Director gets the concrete builder object from the client
        // (application code). That's because application knows better which
        // builder to use to get a specific product.
        CarBuilder builder = new CarBuilder();
        director.constructSportsCar(builder);

        // The final product is often retrieved from a builder object, since
        // Director is not aware and not dependent on concrete builders and
        // products.
        Car car = builder.getResult();
        System.out.println("Car built:\n" + car.getCarType());


        CarManualBuilder manualBuilder = new CarManualBuilder();

        // Director may know several building recipes.
        director.constructSportsCar(manualBuilder);
        Manual carManual = manualBuilder.getResult();
        System.out.println("\nCar manual built:\n" + carManual.print());
    }

}

OutputDemo.txt: 執行結果

Car built:
SPORTS_CAR

Car manual built:
Type of car: SPORTS_CAR
Count of seats: 2
Engine: volume - 3.0; mileage - 0.0
Transmission: SEMI_AUTOMATIC
Trip Computer: Functional
GPS Navigator: Functional

與其他模式的關係

Prototype

原型模式是一種建立型設計模式, 使你能夠複製已有物件, 而又無需使程式碼依賴它們所屬的類。

背景

如果你有一個物件, 並希望生成與其完全相同的一個複製品, 你該如何實現呢? 
首先, 你必須新建一個屬於相同類的物件。 
然後, 你必須遍歷原始物件的所有成員變數, 並將成員變數值複製到新物件中。

不錯! 但有個小問題。 
並非所有物件都能通過這種方式進行復制, 因為有些物件可能擁有私有成員變數, 它們在物件本身以外是不可見的。

直接複製還有另外一個問題。 
因為你必須知道物件所屬的類才能建立複製品, 所以程式碼必須依賴該類。 
即使你可以接受額外的依賴性, 
那還有另外一個問題: 有時你只知道物件所實現的介面, 而不知道其所屬的具體類, 
比如可向方法的某個引數傳入實現了某個介面的任何物件。

原型模式將克隆過程委派給被克隆的實際物件。 模式為所有支援克隆的物件聲明瞭一個通用介面, 該介面讓你能夠克隆物件, 同時又無需將程式碼和物件所屬類耦合。 通常情況下, 這樣的介面中僅包含一個 克隆方法。

所有的類對 克隆方法的實現都非常相似。 該方法會建立一個當前類的物件, 然後將原始物件所有的成員變數值複製到新建的類中。 你甚至可以複製私有成員變數, 因為絕大部分程式語言都允許物件訪問其同類物件的私有成員變數。

支援克隆的物件即為原型。 當你的物件有幾十個成員變數和幾百種類型時, 對其進行克隆甚至可以代替子類的構造。

其運作方式如下: 建立一系列不同型別的物件並不同的方式對其進行配置。 如果所需物件與預先配置的物件相同, 那麼你只需克隆原型即可, 無需新建一個物件。

適用場景

  • 如果你需要複製一些物件, 同時又希望程式碼獨立於這些物件所屬的具體類, 可以使用原型模式。
  • 如果子類的區別僅在於其物件的初始化方式, 那麼你可以使用該模式來減少子類的數量。 別人建立這些子類的目的可能是為了建立特定型別的物件。

例項-實現

使用示例: Java 的 Cloneable (可克隆) 介面就是立即可用的原型模式。

任何類都可通過實現該介面來實現可被克隆的性質。

識別方法: 原型可以簡單地通過 clonecopy等方法來識別。

複製圖形

讓我們來看看在不使用標準 Cloneable介面的情況下如何實現原型模式。

shapes: 形狀列表

shapes/Shape.java: 通用形狀介面

import java.util.Objects;

public abstract class Shape {
    public int x;
    public int y;
    public String color;

    public Shape() {
    }

    public Shape(Shape target) {
        if (target != null) {
            this.x = target.x;
            this.y = target.y;
            this.color = target.color;
        }
    }

    public abstract Shape clone();

    @Override
    public boolean equals(Object object2) {
        if (!(object2 instanceof Shape)) return false;
        Shape shape2 = (Shape) object2;
        return shape2.x == x && shape2.y == y && Objects.equals(shape2.color, color);
    }
}

shapes/Circle.java: 簡單形狀

public class Circle extends Shape {
    public int radius;

    public Circle() {
    }

    public Circle(Circle target) {
        super(target);
        if (target != null) {
            this.radius = target.radius;
        }
    }

    @Override
    public Shape clone() {
        return new Circle(this);
    }

    @Override
    public boolean equals(Object object2) {
        if (!(object2 instanceof Circle) || !super.equals(object2)) return false;
        Circle shape2 = (Circle) object2;
        return shape2.radius == radius;
    }
}

shapes/Rectangle.java: 另一個形狀

public class Rectangle extends Shape {
    public int width;
    public int height;

    public Rectangle() {
    }

    public Rectangle(Rectangle target) {
        super(target);
        if (target != null) {
            this.width = target.width;
            this.height = target.height;
        }
    }

    @Override
    public Shape clone() {
        return new Rectangle(this);
    }

    @Override
    public boolean equals(Object object2) {
        if (!(object2 instanceof Rectangle) || !super.equals(object2)) return false;
        Rectangle shape2 = (Rectangle) object2;
        return shape2.width == width && shape2.height == height;
    }
}

Demo.java: 克隆示例

import java.util.ArrayList;
import java.util.List;

public class Demo {
    public static void main(String[] args) {
        List<Shape> shapes = new ArrayList<>();
        List<Shape> shapesCopy = new ArrayList<>();

        Circle circle = new Circle();
        circle.x = 10;
        circle.y = 20;
        circle.radius = 15;
        circle.color = "red";
        shapes.add(circle);

        Circle anotherCircle = (Circle) circle.clone();
        shapes.add(anotherCircle);

        Rectangle rectangle = new Rectangle();
        rectangle.width = 10;
        rectangle.height = 20;
        rectangle.color = "blue";
        shapes.add(rectangle);

        cloneAndCompare(shapes, shapesCopy);
    }

    private static void cloneAndCompare(List<Shape> shapes, List<Shape> shapesCopy) {
        for (Shape shape : shapes) {
            shapesCopy.add(shape.clone());
        }

        for (int i = 0; i < shapes.size(); i++) {
            if (shapes.get(i) != shapesCopy.get(i)) {
                System.out.println(i + ": Shapes are different objects (yay!)");
                if (shapes.get(i).equals(shapesCopy.get(i))) {
                    System.out.println(i + ": And they are identical (yay!)");
                } else {
                    System.out.println(i + ": But they are not identical (booo!)");
                }
            } else {
                System.out.println(i + ": Shape objects are the same (booo!)");
            }
        }
    }
}

OutputDemo.txt: 執行結果

0: Shapes are different objects (yay!)
0: And they are identical (yay!)
1: Shapes are different objects (yay!)
1: And they are identical (yay!)
2: Shapes are different objects (yay!)
2: And they are identical (yay!)

與其他模式的關係