這篇來介紹一下工廠方法模式(Factory Method Pattern),在實際開發過程中我們都習慣於直接使用 new 關鍵字用來創建一個對象,可是有時候對象的創造需要一系列的步驟:你可能需要計算或取得對象的初始設置;選擇生成哪個子對象實例;或在生成你需要的對象之前必須先生成一些輔助功能的對象,這個時候就需要了解該對象創建的細節,也就是說使用的地方與該對象的實現耦合在了一起,不利於擴展,爲了解決這個問題就需要用到我們的工廠方法模式,它適合那些創建複雜的對象的場景,工廠方法模式也是一個使用頻率很高的設計模式。
設計模式總目錄
特點
工廠方法模式(Factory Method Pattern)定義了一個創建對象的接口,但由子類決定要實例化的類是哪一個,工廠方法讓類把實例化推遲到子類,這樣的設計將對象的創建封裝其來,以便於得到更鬆耦合,更有彈性的設計。
工廠方法模式是創建型設計模式之一,是結構較爲簡單的一種模式,在我們平時的開發過程中應用也是非常的廣泛,比如 ArrayList,HashSet,與 Iterator 之間就能算是一種工廠方法。
簡單工廠模式(Simple Factory)是工廠方法模式的一種,工廠方法模式的特點總結一下:
- 簡單工廠模式從某種意義上來說不算是真正的設計模式,但仍不失爲一個簡單的方法,可以將客戶程序從具體類中解耦;
- 工廠方法模式使用繼承,把對象的創建委託給子類,子類實現工廠方法來創建對象,也就是說允許將實例化延遲到子類進行;;
- 工廠方法模式是一個非常典型的“針對抽象編程,而不是具體類編程”例子。
UML類圖
上圖爲工廠方法模式的uml類圖,幾個角色的分工也很明確,主要分爲四大模塊:
- 一是抽象工廠接口,其爲工廠方法模式的核心,它定義了一個工廠類所具備的基本行爲;
- 二是具體工廠,其實現了具體的業務邏輯;
- 三是抽象產品接口,它定義了所有產品的公共行爲;
- 四是具體產品,爲實現抽象產品的某個具體產品的對象。
簡單工廠模式和工廠方法模式的區別就在於簡單工廠模式將抽象工廠接口這個角色給精簡掉了,是工廠方法模式的一個弱化版本。
從這種設計的角度來思考,工廠方法模式是完全符合設計原則的,它將對象的創建封裝起來,以便於得到更鬆耦合,更有彈性的設計,而且工廠方法模式依賴於抽象的接口,將實例化的任務交給子類去完成,有非常好的可擴充性。
示例與源碼
我們以一個簡單的玩具工廠爲例,工廠中生產小孩的玩具,女生的玩具和男生的玩具,先寫一個 IToy 的抽象產品接口用來定義玩具的基本行爲模式,然後實現該接口生成幾個玩具的具體產品類 ChildrenToy,MenToy 和 WomenToy 類:
IToy.class
public interface IToy {
/**
* 名字
*/
String getName();
/**
* 價格
*/
float price();
/**
* 玩
*/
void play();
}
ChildrenToy.class
public class ChildrenToy implements IToy{
@Override
public String getName() {
return "toy car";
}
@Override
public float price() {
return 10.5f;
}
@Override
public void play() {
Log.e("play", "a child is playing a toy car");
}
}
MenToy.class
public class MenToy implements IToy{
@Override
public String getName() {
return "PS4";
}
@Override
public float price() {
return 2300;
}
@Override
public void play() {
Log.e("play", "a man is playing GTA5 on ps4");
}
}
WomenToy.class
public class WomenToy implements IToy{
@Override
public String getName() {
return "plush toy";
}
@Override
public float price() {
return 200;
}
@Override
public void play() {
Log.e("play", "a woman is playing with a plush toy");
}
}
完成產品的兩個角色之後,接下來要定義工廠類的兩個角色,根據工廠方法模式和簡單工廠模式的不同,可以有兩種不同的寫法:
工廠方法
工廠方法模式需要先寫出一個工廠類的抽象接口來定義行爲,這個時候根據實際情況我們可以分爲兩種實現方式,第一種寫法會有多個 ConcreteFactory 的角色;第二種寫法只會有一個 ConcreteFactory 的角色,根據傳入的參數不同而返回不同的產品對象:
multi ConcreateFactory
IToyCreator.class
public interface IToyCreator {
/**
* 生產玩具
*/
IToy createToy();
}
ChildrenToyCreator.class
public class ChildrenToyCreator implements IToyCreator {
private static final String TAG = "ChildrenToyCreator";
@Override
public IToy createToy() {
IToy toy = new ChildrenToy();
Log.e(TAG, "buy a/an " + toy.getName()+" for " + toy.price() + " yuan, and then ---");
toy.play();
return toy;
}
}
MenToyCreator.class
public class MenToyCreator implements IToyCreator {
private static final String TAG = "MenToyCreator";
@Override
public IToy createToy() {
IToy toy = new MenToy();
Log.e(TAG, "buy a/an " + toy.getName()+" for " + toy.price() + " yuan, and then ---");
toy.play();
return toy;
}
}
WomenToyCreator.class
public class WomenToyCreator implements IToyCreator {
private static final String TAG = "WomenToyCreator";
@Override
public IToy createToy() {
IToy toy = new WomenToy();
Log.e(TAG, "buy a/an " + toy.getName()+" for " + toy.price() + " yuan, and then ---");
toy.play();
return toy;
}
}
最後直接可以根據需要創建不同的 IToy 對象了,測試代碼如下:
IToyCreator toyCreator;
switch (v.getId()) {
case R.id.btn_child:
toyCreator = new ChildrenToyCreator();
toyCreator.createToy();
break;
case R.id.btn_men:
toyCreator = new MenToyCreator();
toyCreator.createToy();
break;
case R.id.btn_women:
toyCreator = new WomenToyCreator();
toyCreator.createToy();
break;
}
single ConcreteFactory
IToyCreator.class
public interface IToyCreator {
/**
* 生產玩具
*/
<T extends IToy> IToy createToy(Class<T> clazz);
}
ConcreteToyCreator.class
public class ConcreteToyCreator implements IToyCreator{
private static final String TAG = "ConcreteToyCreator";
@Override
public <T extends IToy> IToy createToy(Class<T> clazz) {
if (clazz == null){
throw new IllegalArgumentException("argument must not be null");
}
try {
IToy toy = clazz.newInstance();
Log.e(TAG, "buy a/an " + toy.getName()+" for " + toy.price() + " yuan, and then ---");
toy.play();
return toy;
} catch (Exception e) {
throw new UnknownError(e.getMessage());
}
}
}
這種寫法直接傳入一個 Class 對象,接着利用反射的方式進行對象的創建,可以說從某種意義上精簡了很多的工廠實現類,不用一個具體產品類就對應需要一個具體工廠類了,下面爲測試代碼:
IToyCreator toyCreator;
switch (v.getId()) {
case R.id.btn_child:
toyCreator.createToy(ChildrenToy.class);
break;
case R.id.btn_men:
toyCreator.createToy(MenToy.class);
break;
case R.id.btn_women:
toyCreator.createToy(WomenToy.class);
break;
}
總結對比
以上的兩種方式當然最後都能夠成功打印出正確的結果:
單個工廠實現類的方法對比前面的多個工廠實現類的方法來說更加的簡潔和動態,而且對於以後新增的產品類來說也能夠不用修改原來的代碼,符合開閉原則,但是這種寫法在某些情況下是不適用的,比如不同的 IToy 對象設置了不同的構造函數,參數都不一樣,用反射來實現就不適用了,這個時候就只能爲每一個具體產品類都定義一個對應的具體工廠類了。
簡單工廠
同樣是上面的代碼,具體工廠實現類只有一個的時候,我們還是爲工廠提供了一個抽象類,那麼,如果將 IToyCreator 這個角色精簡掉,只留下 ConcreteToyCreator 的這個角色,將其中的產品生成方法設置爲靜態應該也是沒問題的:
public class ToyCreator{
private static final String TAG = "ToyCreator";
public static <T extends IToy> IToy createToy(Class<T> clazz) {
if (clazz == null){
throw new IllegalArgumentException("argument must not be null");
}
try {
IToy toy = clazz.newInstance();
Log.e(TAG, "buy a/an " + toy.getName()+" for " + toy.price() + " yuan, and then ---");
toy.play();
return toy;
} catch (Exception e) {
throw new UnknownError(e.getMessage());
}
}
}
像這樣的方式就稱爲簡單工廠模式,上面也說過,是工廠方法模式的一個弱化版本,缺點就是失去了被子類繼承的特性,所有的壓力都集中在工廠類中,不利於維護。
總結
總的來說,工廠方法模式是一個很好的設計模式,它遵循了一個“儘可能讓事情保持抽象”的原則,鬆耦合的設計原則也能夠很好的符合開閉原則,將類的實例化推遲到子類,同時也擯棄了簡單工廠模式的缺點。
但是同時工廠方法模式也有一些缺點,每次我們爲工廠方法添加新的產品時就要編寫一個新的產品類,同時還要引入抽象層,當產品種類非常多時,會出現大量的與之對應的工廠對象,這必然會導致類結構的複雜化,所以對於簡單的情況下,使用工廠方法模式就需要考慮是不是有些“重”了。
源碼下載
https://github.com/Jakey-jp/Design-Patterns/tree/master/FactoryMethodPattern
引用
http://blog.csdn.net/jason0539/article/details/23020989
https://en.wikipedia.org/wiki/Factory_method_pattern
http://news.tuxi.com.cn/to/kf/satmaj/namtst.html