設計模式: Factory Method 工廠方法模式
技術標籤:design_pattern設計模式java多型工廠方法模式factory method
設計模式: Factory Method 工廠方法模式
文章目錄
簡介
目的 | 建立型 | 結構型 | 行為型 |
---|---|---|---|
類 | Factory Method 工廠方法 | Adapter 介面卡 | Interpreter 直譯器 Template Method 模版方法 |
物件 | Abstract Factory 抽象工廠 Builder 生成器 Singleton 單例 | Adapter 介面卡 Bridge 橋接 Composite 組合 Decorator 裝飾器 Facade 外觀 Flyweight 享元 Proxy 代理 | Chain of Responsibility 職責鏈 Command 命令 Iterator 迭代器 Mediator 中介者 Memento 備忘錄 Observer 觀察者 State 狀態 Strategy 策略 Visitor 訪問者 |
- 回顧
接著上一篇的設計模式: Abstract Factory 抽象工廠模式,我們在建立物件的時候發現一種模式,為產品型別創造出公共介面,並建立抽象工廠的共同模式來提取建立一組產品
- 改善
然而在實際開發的時候仍然可能存在一個問題,開發時各個’產品’並不一定存在公共表現或是沒有到’一組’產品那麼多的型別。建立抽象產品組和抽象工廠的開銷遠比實際上需要管理的產品物件要複雜許多,實際上我們可能只是想單獨為某個型別的產品開發多個實現而已,這時候我們就可以使用Factory Method 工廠方法模式。
參考
Design Patterns-Elements of Reusable Object-Oriented Software |
完整示例程式碼
正文
場景
與前面的抽象工廠相比,工廠方法更適合於產品型別較少,而對實際產品型別的擴充套件性要求更高的場景
在這樣的場景之下,我們希望免去建立抽象工廠型別的開銷,同時避免為了維護共同抽象產品組介面以及各個工廠的一致性(實現抽象方法)。這時候我們可以從抽象工廠型別中提取出用於建立單一個產品型別的工廠方法,並將其放入到實際使用或是對實際產品型別進行再加工的類當中。
如此一來相當於是將負責生產多個產品的’工廠’的職責縮小成負責單一產品的’建立者’,同時透過繼承並實現建立者中宣告用於建立產品的方法來實現產品的多型。;廠方法也是由抽象工廠中抽取出單一個用於生產產品的方法而命名的。
模式結構
- Product 抽象產品類:需要創造的產品的公共介面
- Concrete Product 實際產品類:真正建立的產品型別,繼承抽象產品類並實現產品的多型
- Creator 建立者(抽象類):宣告建立產品的抽象工廠方法,並定義其他對產品加工或需要使用產品的方法(這邊的重點專注於物件的建立)
- Concrete Creator 實際建立者:繼承並實現真正建立產品的工廠方法(其他加工方法已經在抽象類中定義,在不同產品實現下依舊相同的公共方法)
由圖中我們可以看到:我們將原來的抽象工廠中的一個個工廠方法切分出來,並將該職責分派給實際建立並加工產品的建立者,相當於將建立的職責縮小為建立單一型別的產品,增加了對產品變化的靈活性
程式碼實現
下面我們直接根據上面的型別結構做一個簡單的實現示意。在真正的應用當中應該要去思考系統中的’產品’型別的抽象以及建立抽象工廠的必要性,最後才決定使用工廠方法或是抽象工廠
Products 產品類
首先先定義好抽象產品類和實際產品實現
/* Product.java */
public interface Product {}
/* ProductA.java */
public class ProductA implements Product {}
/* ProductB.java */
public class ProductB implements Product {}
Creator 建立者
接下來是負責建立產品的建立者類
/* Creator.java */
public abstract class Creator {
public abstract Product createProduct();
public void operation() {
Product product = createProduct();
System.out.println("using Product " + product);
}
}
我們先在抽象類中定義好用於加工產品的方法,並留下負責建立產品的工廠方法(抽象方法);下面我們定義具體的建立者來實現不同實際產品的建立
/* CreatorA.java */
public class CreatorA extends Creator {
@Override
public Product createProduct() {
return new ProductA();
}
}
/* CreatorB.java */
public class CreatorB extends Creator {
@Override
public Product createProduct() {
return new ProductB();
}
}
測試類
最後給出測試用程式碼和結果
/* CreatorTest.java */
public class CreatorTest {
private void testTemplate(Creator creator) {
creator.operation();
}
@Test
public void test_creatorA() {
testTemplate(new CreatorA());
}
@Test
public void test_creatorB() {
testTemplate(new CreatorB());
}
}
using Product [email protected]
using Product [email protected]
補充:靜態工廠方法
接下來我們補充一個在實踐中也經常使用的工廠方法模式的變種:靜態工廠方法(Static Factory Method)
在前面的工廠方法中我們透過實現抽象方法在管理產品的建立的同時完成產品型別的多型,然而工廠方法更多的是要表達對於物件建立的管理。然而我們可以在更進一步,透過靜態方法的形式(或是語言的特性)來統一管理特定型別所有物件的建立。
下面我們以 Java 實現舉例
靜態工具類
第一種我們接續上面用到的產品型別,單獨建立一個類,併為其新增靜態的、用於建立不同物件的類。由於這個類本身並不需要是一個物件,我們可以宣告為靜態的方法,使得這些工廠方法是繫結在該工具類的名稱空間之下,就好像一個放在全域性的函式一樣。
package com.example.factory_method.classic;
/* StaticCreator.java */
public class StaticCreator {
public static ProductA createProductA() {
return new ProductA();
}
public static ProductB createProductB() {
return new ProductB();
}
// ...
}
你說這樣就沒法多型了?確實,要為每一種產品實現維護一個工廠方法相對來說還是略顯麻煩(不過這也是系統初期剛開始建立產品型別時抽象程度不高的基礎辦法,讀者也要切記不可為了抽象而抽象,而是在產品型別到達一定規模之後存在必要性或其他優化、管理需求,才需要進行更進一步的抽象),我們還可以藉助 Java 的反射機制一勞永逸。
/* StaticCreator.java */
public class StaticCreator {
/* createProductA ... */
/* createProductB ... */
public static Product createProduct(Class<? extends Product> clazz) throws IllegalAccessException, InstantiationException {
return clazz.newInstance();
}
public static Product createProduct(String type) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return ((Class<? extends Product>) Class.forName(type)).newInstance();
}
}
- 測試程式碼
/* StaticCreatorTest.java */
public class StaticCreatorTest {
@Test
public void test() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Product product = StaticCreator.createProductA();
System.out.println(product);
product = StaticCreator.createProductB();
System.out.println(product);
product = StaticCreator.createProduct(ProductA.class);
System.out.println(product);
product = StaticCreator.createProduct("com.example.factory_method.classic.ProductB");
System.out.println(product);
}
}
- 測試結果
[email protected]
[email protected]
[email protected]
[email protected]
私有建構函式
第二種我們可以透過私有化(private
或 protected
)建構函式來限制其他類來例項化(new
)產品,同時再對外暴露一個靜態的工廠方法用於建立物件如下
/* ProductC */
public class ProductC {
private ProductC() {}
public static ProductC create() {
return new ProductC();
}
}
如此一來所有 ProductC 物件的建立都必須經過靜態的 create 方法來建立,如果我們想要對建立進行記錄、加上其他限制或產生其他副作用的話都能夠加在工廠方法內部
private static instances = new ProductC[100];
private static int count = 0; // 建立例項的計數
public static ProductC create() {
ProductC productC = count >= 100 ? instances[count % 100] : (instances[count] = new ProductC()); // 限制例項個數
record("create " + count); // 建立方法呼叫記錄(副作用)
count++;
return new ProductC();
}
同時使用工廠方法也能夠使得使用者更具體的瞭解並給出建立產品時需要的關鍵資訊,如下工廠方法返回一個 Spring MVC 的 Http 響應物件
/* ResponseUtils.java */
public class ResponseUtils {
public static ResponseEntity<Object> create(Object data, HttpStatus status) {
HttpHeaders headers = new HttpHeaders();
headers.setContentTypt(MediaType.APPLICATION_JSON);
return new ReponseEntity<>(data, headers, status);
}
}
結語
本篇介紹了抽象工廠的派生模式 - Factory Method 工廠方法模式,相當於是對抽象工廠的切分,在避免了維護抽象工廠的額外開銷之下同時保證的產品型別的穩定性和建立的管理,是應對產品種類不多時的更好的實現。