一.概述
平時做項目跟使用第三方類庫的時候經常會用到工廠模式.什麼是工廠模式,簡單來說就是他的字面意思.給外部批量提供相同或者不同的產品,而外部不需要關心工廠是如何創建一個複雜產品的過程.所以工廠模式可以降低模塊間的耦合,同時可以提高擴展性(當有新的產品出現時,只需要擴展工廠就行了,上層模塊不敏感).
工廠模式根據抽象的角度和層級的不同可以分爲兩種模式:
1.工廠方法模式 (Factory Method)
2.抽象工廠模式 (Abstract Factory)
二.實現
1.工廠方法模式
工廠方法模式的特點是:
一個抽象產品類(或接口),派生(或實現)多個真實產品類
一個抽象工廠類(或接口),派生(或實現)多個真實工廠類
一般來說標準的工廠方法模式需要一個工廠只生產一種產品,那麼當產品種類特別多的時候工廠的數量就特別多,所以通常會使用一些工廠方法模式的變種.
1.)標準工廠方法模式
首先先介紹一下標準的工廠方法模式,不帶任何的變種.以工廠生產不同操作系統的手機爲例.
建立一個產品接口,提供一個獲取系統信息的方法.
/**
* Created by jesse on 15-8-17.
*/
public interface IPhone {
public void getOS();
}
再根據IPhone接口實現Android,IOS,Blackberry三種手機.
public class AndroidPhone implements IPhone {
private final String TAG = AndroidPhone.class.getSimpleName();
@Override
public void getOS() {
Log.i(TAG, "im Android");
}
}
public class IosPhone implements IPhone {
private final String TAG = IosPhone.class.getSimpleName();
@Override
public void getOS() {
Log.i(TAG, "im IOS");
}
}
public class BlackBerryPhone implements IPhone {
private final String TAG = BlackBerryPhone.class.getSimpleName();
@Override
public void getOS() {
Log.i(TAG, "im BlackBerry");
}
}
標準的工廠方法模式都需要有抽象的工廠接口或者基類.
public abstract class IGenerator {
public abstract IPhone generatePhone(String flag) throws Exception;
}
通過基類或者接口來實現真實的工廠類,這裏需要注意跟簡單工廠模式的不同,標準的工廠方法模式裏面一個工廠只生產一個產品,所以這裏要根據產品的種類劃分出來三個工廠,分別生產三種不同的產品.這種設計思想非常契合單一職責原則.
public class AndroidGenerator extends IGenerator {
@Override
public IPhone generatePhone() {
return new AndroidPhone();
}
}
public class IOSGenerator extends IGenerator {
@Override
public IPhone generatePhone() {
return new IosPhone();
}
}
public class BlackberryGenerator extends IGenerator {
@Override
public IPhone generatePhone() {
return new BlackBerryPhone();
}
}
在客戶端從工廠中獲得產品並使用的過程中都是通過接口進行訪問的,在創建產品的階段有效得降低了使用者和產品之間的耦合度.
IPhone android, ios, bb;
IGenerator androidGenerator, iosGenerator, bbGenerator;
androidGenerator = new AndroidGenerator();
iosGenerator = new IOSGenerator();
bbGenerator = new BlackberryGenerator();
android = androidGenerator.generatePhone();
ios = iosGenerator.generatePhone();
bb = bbGenerator.generatePhone();
android.getOS();
ios.getOS();
bb.getOS();
最終的運行效果顯而易見.
2).簡單工廠模式
接着分析一下簡單工廠模式,這是最簡單的變種,也叫做靜態工廠方法模式,從這個名字就可以看出工廠的方法是靜態的.既然工廠方法是靜態的,那麼工廠就不能通過繼承進行擴展,如果有新增的產品,就只能在靜態方法裏面做修改所以從這個角度來說簡單工廠模式是不符合開閉原則的.
因爲這是靜態工廠方法模式,所以工廠類就沒有接口或者虛基類來提供抽象.通過不同的Flag來初始化不同的產品.
public class PhoneGenerator{
public final static String GENERATE_IOS = "generate_ios";
public final static String GENERATE_ANDROID = "generate_android";
public final static String GENERATE_BLACKBERRY = "generate_blackberry";
public static IPhone generatePhone(String flag) throws Exception {
IPhone iPhone = null;
switch (flag){
case GENERATE_ANDROID:
iPhone = new AndroidPhone();
break;
case GENERATE_IOS:
iPhone = new IosPhone();
break;
case GENERATE_BLACKBERRY:
iPhone = new BlackBerryPhone();
break;
default:
throw new Exception("UNDEFINED FLAG");
}
return iPhone;
}
}
對外部來說要使用工廠只需要把目標產品類傳過去就行了.運行結果跟1)中的是一樣的.
IPhone android, ios, bb;
android = PhoneGenerator.generatePhone(PhoneGenerator.GENERATE_ANDROID);
ios = PhoneGenerator.generatePhone(PhoneGenerator.GENERATE_IOS);
bb = PhoneGenerator.generatePhone(PhoneGenerator.GENERATE_BLACKBERRY);
android.getOS();
ios.getOS();
bb.getOS();
3)結合反射的應用
假設需要加入一種搭載win10系統的手機,標準的工廠方法模式需要重新派生出來一個新的工廠來給客戶使用,簡單工廠模式也需要新增新的flag和case判斷去構造新的手機.有沒有什麼方法可以儘量避免這些修改呢?當然是有的,這裏可以通過使用Class.forName 反射的方式來達到目的.
首先通過泛型來約束輸入輸出的參數類型,把異常拋到上層去處理並實現具體的工廠.
public abstract class IGenerator {
public abstract <T extends IPhone>T generatePhone(Class<T> clazz) throws Exception;
}
public class PhoneGenerator extends IGenerator {
public <T extends IPhone>T generatePhone(Class<T> clazz) throws Exception {
IPhone iPhone = null;
iPhone = (IPhone) Class.forName(clazz.getName()).newInstance();
return (T)iPhone;
}
}
通過這種裝載的方式去初始化產品就可以達到上面描述的需求,可以根據需求直接添加一個實現了IPhone接口的WindowsPhone產品而不需要修改工廠,客戶就可以直接從工廠拿到WindowsPhone的手機去使用了.
4)產品類私有構造應用
產品類私有構造應用其實更偏向與一種規範.既然使用工廠模式了,那就是這些手機全部都要在工廠內部創建出來.這種應用就做了限制,使用私有構造就不允許外部通過new的方式來創建,而工廠則通過反射和更改訪問權限來創建產品.當然這個時候外部也可以通過同樣的方式來創建對象,所以說這個應用更偏向於一種團隊規範.
public class PhoneGenerator extends IGenerator {
public <T extends IPhone>T generatePhone(Class<T> clazz) throws Exception {
IPhone iPhone = null;
Class phone = Class.forName(clazz.getName());
phone.getDeclaredConstructor().setAccessible(true);
iPhone = (IPhone) phone.newInstance();
return (T)iPhone;
}
}
5)緩存對象
對於那些創建起來特別消耗資源或者特別複雜的對象,可以使用下面的方式來進行一個長期的緩存.對於那些有訪問數量需求的對象也可以建立緩存List,通過設置最大創建數來控制對象量級的峯值.例如JDBC的最大連接數等.
public class PhoneGenerator extends IGenerator{
private Map<String, IPhone> map = new HashMap<>();
@Override
public <T extends IPhone> T generatePhone(Class<T> clazz) throws Exception{
IPhone iPhone = null;
if (map.containsKey(clazz.getName()))
iPhone = map.get(clazz.getName());
else {
iPhone = (IPhone) Class.forName(clazz.getName()).newInstance();
map.put(clazz.getName(), iPhone);
}
return (T) iPhone;
}
}
2.抽象工廠模式
抽象工廠模式的特點:
多個抽象產品類(或接口),派生(或實現)多個真實產品類
一個抽象工廠類(或接口),派生(或實現)多個真實工廠類
抽象工廠模式其實也算是工廠方法模式的一種延伸,在工廠方法模式中所有的產品都是一個系列的,都是從IPhone那裏實現出的不同真實產品,所以對於外部來說他們都是手機產品,只需要關心手機的抽象接口就行了.然而又多個業務種類,並且這些業務有些依賴關係的時候,這種情況下使用的工廠模式就是抽象工廠模式.
接着在抽象方法模式裏面的例子,在抽象工廠模式中我們需要再多開一種產品,那就平板吧,而平板又分爲Android平板和IOS平板(這裏偷懶黑莓平板就不寫了),並且平板跟手機有很多共同的地方,例如相同的OS硬件設計等.既然是一個新的產品線了,那麼還是先抽象出來平板的接口來.還是老樣子,打印一下自己是什麼系統的.
public interface IPad {
public void getBrand();
}
接着通過IPad接口來實現兩個不同的平板.
public class AndroidPad implements IPad {
private final String TAG = AndroidPad.class.getSimpleName();
@Override
public void getBrand() {
Log.i(TAG, "im Android pad");
}
}
public class IOSPad implements IPad {
private final String TAG = IOSPad.class.getSimpleName();
@Override
public void getBrand() {
Log.i(TAG, "im IOS phone pad");
}
}
在抽象工廠的接口的時候還是繼續使用泛型來創建了,這樣也省的派生出來幾個不同的工廠.
public abstract class IGenerator {
public abstract <T extends IPhone>T generatePhone(Class<T> clazz) throws Exception;
public abstract <T extends IPad>T generatePad(Class<T> clazz) throws Exception;
}
public class ProductGenerator extends IGenerator{
@Override
public <T extends IPhone> T generatePhone(Class<T> clazz) throws Exception{
IPhone iPhone = (IPhone) Class.forName(clazz.getName()).newInstance();
return (T) iPhone;
}
@Override
public <T extends IPad> T generatePad(Class<T> clazz) throws Exception {
IPad iPad = (IPad) Class.forName(clazz.getName()).newInstance();
return (T) iPad;
}
}
假設有一個客戶需要來我廠定製移動產品,A套餐中包含一個IOS手機和一個Android的平板,B套餐中包含一個Android手機和一個IOS平板.而這個套餐規則可以通過工廠進行約束.這樣工廠就能勝任完成這個需求的任務了.
然而恰恰因爲抽象工廠模式支持多種產品線,結果導致需要擴展一條新的產品的時候就會比較麻煩.假設需要新增一個屏幕貼膜產品,並且給每個出廠的帶屏幕的產品都配一個.那麼要做的修改不僅僅是要添加貼膜這個產品,還要修改從工廠的抽象到工廠的實現,還要修改工廠的約束.這是不符合開閉原則的.但是如果只是擴展一個產品的子系列,例如要新增一個windows平板,抽象工廠模式和工廠方法模式一樣根本不需要修改工廠抽象和工廠實現,只需要新增產品就行了.