工廠方法(學習筆記)
1. 意圖
定義一個用於建立物件的介面,讓子類決定例項化哪一個類。Factory Method使一個類的例項化延遲到其子類。
2. 別名
虛構造器(virtual constructor)
3. 動機(參考Design pattern)
框架使用抽象類定義和維護物件之間的關係。這些物件關係的建立也通常由框架負責。
考慮一個向用戶顯示多個文件的應用框架。在這個框架中,兩個主要的抽象類Application和Document。客戶通過重定義其子類實現具體應用。例如,定義一個繪圖應用,定義類DrawingApplication和DrawingDocument。Application類負責管理Document類並根據需求建立它們——例如,當用戶從選單選擇Open或New的時候。
因為被例項化的特定Document子類是與特定應用相關的,所以Application類不可能預測到哪個Document子類被例項化。
Factory Method模式封裝了哪個Document子類將被建立的資訊並將這些資訊從框架中分離出來,如下圖所示。
Application 的子類重定義Application的抽象操作CreateDocument已返回適當的Document子類物件。Application子類例項化後,其可以例項化與文件相關的類而無須知道這些文件的類。稱CreateDocument是一個工廠方法,它負責生產一個物件。
4. 適用性
- 當一個類不知道它所必須建立的物件的類的時候
- 當一個類希望由它的子類來制定他所建立的物件的時候
- 複用現有物件來節省系統資源,而不是每次都重新建立物件
在處理大型資源密集型物件(比如資料庫連線、檔案系統和網路資源)時,經常會碰到這種資源需求
複用現有物件的方法:
1. 建立儲存空間存放所有已建立的物件
2. 當他人請求一個物件時,程式將在物件池中搜索可用物件
3. 然後將其返回給客戶端程式碼
4. 如果沒有可用物件,就建立一個新物件,並新增到物件池中
5. 結構
6. 效果
1)可以避免建立者和具體產品之間的緊密耦合
2)單一職責原則。 可以將產品建立程式碼放在程式的單一位置, 從而使得程式碼更容易維護
3) 開閉原則。 無需更改現有客戶端程式碼, 你就可以在程式中引入新的產品型別
7. 程式碼實現
buttons
buttons/Button.java: 通用產品介面
package factory_method.buttons; /** * @author GaoMing * @date 2021/7/18 - 11:43 * Common interface for all buttons. */ public interface Button { void render(); void onClick(); }
buttons/HtmlButton.java: 具體產品
package factory_method.buttons; /** * @author GaoMing * @date 2021/7/18 - 11:43 * 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 factory_method.buttons; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; /** * @author GaoMing * @date 2021/7/18 - 11:45 * 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); } }); } }
factories
factories/Dialog.java: 基礎建立者
package factory_method.factories; import factory_method.buttons.Button; /** * @author GaoMing * @date 2021/7/18 - 11:50 * 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(); }
factories/HtmlDialog.java: 具體建立者
package factory_method.factories; import factory_method.buttons.HtmlButton; import factory_method.buttons.Button; /** * @author GaoMing * @date 2021/7/18 - 11:54 */ public class HtmlDialog extends Dialog{ @Override public Button createButton() { return new HtmlButton(); } }
factories/WindowsDialog.java: 另一個具體建立者
package factory_method.factories; import factory_method.buttons.WindowsButton; import factory_method.buttons.Button; /** * @author GaoMing * @date 2021/7/18 - 11:54 */ public class WindowsDialog extends Dialog{ @Override public Button createButton() { return new WindowsButton(); } }
Demo.java: 客戶端程式碼
package factory_method; import factory_method.factories.HtmlDialog; import factory_method.factories.WindowsDialog; import factory_method.factories.Dialog; /** * @author GaoMing * @date 2021/7/18 - 11:56 * 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.png: 執行結果 (WindowsDialog)
8. 與其他模式的關係
- 可以同時使用工廠方法和迭代器模式來讓子類集合返回不同型別的迭代器, 並使得迭代器與集合相匹配
- 原型並不基於繼承, 因此沒有繼承的缺點。 另一方面, 原型需要對被複制物件進行復雜的初始化。 工廠方法基於繼承, 但是它不需要初始化步驟
- 工廠方法是模板方法模式的一種特殊形式。 同時, 工廠方法可以作為一個大型模板方法中的一個步驟
9. 已知應用
- javax.xml.parsers.DocumentBuilderFactory#newInstance()
- javax.xml.transform.TransformerFactory#newInstance()
- javax.xml.xpath.XPathFactory#newInstance()