抽象工廠模式簡單介紹
抽象工廠模式也是創建型設計模式之一。在上一篇博客中我們已經對工廠方法模式進行了介紹,那麼這個抽象工廠又是怎麼一回事呢?大家聯想一下現實生活中的工廠肯定都是具體的,也就是說每個工廠都會生產某一種具體的產品,那麼抽象工廠意味着生產出來的產品是不確定的,那這豈不是很奇怪?抽象工廠模式起源於以前對不同操作系統的圖形化解決方案,如不同操作系統中的按鈕和文本框控件其實現不同,展示效果也不一樣,對於每一個操作系統,其本身就構成一個產品類,而按鈕與文本框也構成一個產品類,而按鈕與文本框控件也構成一個產品類,兩種產品類兩種變化,各自有自己的特性,如 Android 中的 Button 和 TextView,IOS 中的 Button 和 TextView,WindowPhone 中的 Button 和 TextView 等。
抽象工廠模式的定義
爲創建一組相關或者是相互依賴的對象提供一個接口,而不需要指定它們的具體類。
抽象工廠模式的使用場景
一個對象有相同的約束時可以使用抽象工廠模式。是不是聽起來很抽象?舉個例子,Android、IOS、WindowPhone 下都有短信軟件 和 撥號軟件,兩者都屬於 Software 軟件的範疇,但是,它們所在的操作系統平臺不一樣,即使是同一家公司出品的軟件,其代碼的實現邏輯也是不同的,這個時候就可以考慮使用工廠方法模式來生產 Android、IOS、WindowPhone 下的短信軟件和撥號軟件。還是有點迷糊?沒關係,等我們稍後給出一個抽象工廠的例子之後,你就明白了。
抽象工廠模式的 UML 類圖
類圖畫的不是很好,由於是採用在線工具 UMLer 進行繪製的,它裏面的關係線貌似好像不能折彎,所以關係線出現了重疊,不過大概也可以看明白類與類的關係的。
其實從抽象工廠模式的類圖可以看出它和我們上篇博客中提到了工廠方法模式的類圖基本上是大同小異的。工廠方法類圖是咱們給出的 UML 類圖其實是單個工廠對應單個產品,而抽象工廠模式其實更偏向於多工廠對應多產品這麼一種情況,大家可以看下工廠方法模式的類圖和抽象工廠模式的類圖的區別。
角色介紹:
- Factory:抽象工廠角色,它聲明瞭一組用於創建一種產品的方法,每一個方法對應一種產品,如上述類圖中的 Factory 中就定義了兩個方法,分別用於創建產品A 和 產品B。
- ConcreteFactory:具體工廠角色,它實現了在抽象工廠中定義的抽象產品的方法,生成一組具體的產品,這些產品構成了一個產品種類,每一個產品都位於某個產品等級結構中,如上述類圖中的ConcreteFactoryA 和 ConcreteFactoryB。
- Product:抽象產品角色,它爲每種產品生命接口,比如上述類圖中的 ProductA 和 ProductB。
- ConcreteProduct:具體產品角色,它定義具體工廠生產的具體產品角色,實現抽象產品接口中聲明的業務方法,如上述類圖中的 ConcreteProductA1、ConcreteProductA2、ConcreteProductB1 和 ConcreteProductB2。
根據類圖可以得到一套通用的模板代碼:
//抽象產品類A
public abstract class ProductA {
/**
* 產品類的抽象方法,由具體的產品類去實現
*/
public abstract void method();
}
//抽象產品類B
public abstract class ProductB {
/**
* 產品類的抽象方法,由具體的產品類去實現
*/
public abstract void method();
}
//具體產品類A1
public class ConcreteProductA1 extends ProductA {
@Override
public void method() {
Log.d("Product","我是具體的產品A1");
}
}
//具體產品類A2
public class ConcreteProductA2 extends ProductA {
@Override
public void method() {
Log.d("Product","我是具體的產品A2");
}
}
//具體產品類B1
public class ConcreteProductB1 extends ProductA {
@Override
public void method() {
Log.d("Product","我是具體的產品B1");
}
}
//具體產品類B2
public class ConcreteProductB2 extends ProductA {
@Override
public void method() {
Log.d("Product","我是具體的產品B2");
}
}
//抽象工廠類
public abstract class Factory {
/**
* 抽象工廠方法,具體生產什麼產品由子類去實現
*
* @return
*/
public abstract <T extends ProductA> T createProductA(Class<T> clazz);
public abstract <T extends ProductB> T createProductB(Class<T> clazz);
}
//具體工廠類A
public class ConcreteFactoryA extends Factory {
@Override
public <T extends ProductA> T createProductA(Class<T> clazz) {
ProductA p = null;
try {
p = (ProductA) Class.forName(clazz.getName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return (T) p;
}
@Override
public <T extends ProductB> T createProductB(Class<T> clazz) {
ProductB p = null;
try {
p = (ProductB) Class.forName(clazz.getName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return (T) p;
}
}
//具體工廠類
public class ConcreteFactoryB extends Factory {
@Override
public <T extends ProductA> T createProductA(Class<T> clazz) {
ProductA p = null;
try {
p = (ProductA) Class.forName(clazz.getName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return (T) p;
}
@Override
public <T extends ProductB> T createProductB(Class<T> clazz) {
ProductB p = null;
try {
p = (ProductB) Class.forName(clazz.getName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return (T) p;
}
}
大家可以自己找下感覺,下面我們就開始實戰
抽象工廠方法模式實戰
在上篇博客中的工廠方法模式中,我們已經拿汽車廠爲例闡述了工廠方法模式,但是,假設現在出現了一個問題,雖然 Q3、Q5、Q7 都是一個車系,但是三者之間的零部件差別卻是很大,就拿 Q3 和 Q5 來說,Q3 使用的發動機是國產的,而 Q7 使用的是原裝進口的;Q3 的輪胎是普通的輪胎,而 Q7 則使用的是全尺寸越野輪胎;還有 Q3 使用的是比較普通的制動系統,而 Q7 則使用的是制動性能極好的制動系統。Q3、Q7 對應的是一系列車,而 發動機、輪胎、制動系統則對應的是一系列零件,兩者是兩種不同的產品類型,這時候就可以將抽象工廠模式應用到其中。
我們先來捋一捋思路吧:首先,從上面給出的題目中其實我們就已經知道了很多信息:1、Q3、Q5、Q7是同一系列車;2、Q3、Q5、Q7的零部件不同。那麼我們就要思考了,因爲是汽車廠,是以生產汽車爲主,咱們在上篇博客中之所以採用一條生產線是因爲所有車都是一樣的,所以沒必要開闢多條生產線,而這裏還行麼?雖然車是同一系列車,但是車的零部件不一樣啊?如果還是用一條生產線還能同時生成 3 中車麼?不能了吧?所以這裏我們就要創建 3 條生產線去生成不用系列的車(相對語需要 3 個工廠),而每個工廠再去組裝本系列的車(發動機、輪胎、制動系統),OK了,下面就是代碼實現了:
/**
* 抽象車廠類
* 每個具體的工廠都需要生產本系列車需要的部件
*/
public abstract class AudiFactory {
/**
* 生產輪胎
* @param clazz
* @param <T>
* @return 輪胎
*/
public abstract <T extends ITire> T createTire(Class<T> clazz);
/**
* 生產發動機
* @param clazz
* @param <T>
* @return 發動機
*/
public abstract <T extends IEngine> T createEngine(Class<T> clazz);
/**
* 生產製動系統
* @param clazz
* @param <T>
* @return 制動系統
*/
public abstract <T extends IBrake> T createBrake(Class<T> clazz);
}
/**
* 輪胎接口
*/
public interface ITire {
void tire();
}
/**
* 普通輪胎
*/
public class NormalTire implements ITire {
@Override
public void tire() {
Log.d("Audi","普通輪胎");
}
}
/**
* 越野輪胎
*/
public class SUVTire implements ITire {
@Override
public void tire() {
Log.d("Audi","越野輪胎");
}
}
/**
* 發動機接口
*/
public interface IEngine {
void engine();
}
/**
* 國產發動機
*/
public class DomesticEngine implements IEngine {
@Override
public void engine() {
Log.d("Audi", "國產發動機");
}
}
/**
* 進口發動機
*/
public class ImportEngine implements IEngine {
@Override
public void engine() {
Log.d("Audi", "進口發動機");
}
}
/**
* 制動系統
*/
public interface IBrake {
void brake();
}
/**
* 普通制動系統
*/
public class NormalBrake implements IBrake {
@Override
public void brake() {
Log.d("Audi", "普通制動系統");
}
}
/**
* 高級制動系統
*/
public class SeniorBrake implements IBrake {
@Override
public void brake() {
Log.d("Audi", "高級制動系統");
}
}
/**
* Q3系列車以及零部件生產工廠
*/
public class Q3Factory extends AudiFactory {
/**
* 生產輪胎
* @param clazz
* @param <T>
* @return 輪胎
*/
@Override
public <T extends ITire> T createTire(Class<T> clazz) {
ITire tire = null;
try {
tire = (ITire) Class.forName(clazz.getName()).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return (T) tire;
}
/**
* 生產發動機
* @param clazz
* @param <T>
* @return 發動機
*/
@Override
public <T extends IEngine> T createEngine(Class<T> clazz) {
IEngine engine = null;
try {
engine = (IEngine) Class.forName(clazz.getName()).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return (T) engine;
}
/**
* 生產製動系統
* @param clazz
* @param <T>
* @return 制動系統
*/
@Override
public <T extends IBrake> T createBrake(Class<T> clazz) {
IBrake brake = null;
try {
brake = (IBrake) Class.forName(clazz.getName()).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return (T) brake;
}
}
/**
* Q7系列車以及零部件生產工廠
*/
public class Q7Factory extends AudiFactory {
/**
* 生產輪胎
* @param clazz
* @param <T>
* @return 輪胎
*/
@Override
public <T extends ITire> T createTire(Class<T> clazz) {
ITire tire = null;
try {
tire = (ITire) Class.forName(clazz.getName()).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return (T) tire;
}
/**
* 生產發動機
* @param clazz
* @param <T>
* @return 發動機
*/
@Override
public <T extends IEngine> T createEngine(Class<T> clazz) {
IEngine engine = null;
try {
engine = (IEngine) Class.forName(clazz.getName()).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return (T) engine;
}
/**
* 生產製動系統
* @param clazz
* @param <T>
* @return 制動系統
*/
@Override
public <T extends IBrake> T createBrake(Class<T> clazz) {
IBrake brake = null;
try {
brake = (IBrake) Class.forName(clazz.getName()).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return (T) brake;
}
}
/**
* 客戶類
*/
public class Test {
public static void main() {
//構造一個 Q3 工廠
Q3Factory q3Factory = new Q3Factory();
//生產 Q3 汽車使用的普通輪胎
q3Factory.createTire(NormalTire.class).tire();
//生產 Q3 汽車使用的國產發動機
q3Factory.createEngine(DomesticEngine.class).engine();
//生產 Q3 汽車使用的普通制動系統
q3Factory.createBrake(NormalBrake.class).brake();
Q7Factory q7Factory = new Q7Factory();
//生產 Q7 汽車使用的越野輪胎
q7Factory.createTire(SUVTire.class).tire();
//生產 Q7 汽車使用的進口發動機
q7Factory.createEngine(ImportEngine.class).engine();
//生產 Q7 汽車使用的高級制動系統
q7Factory.createBrake(SeniorBrake.class).brake();
}
}
上面我們只是模擬了 Q3 和 Q7 的工廠,如果此時我們需要增加 Q5 的工廠呢?那麼對應的輪胎、制動系統和發動機類又要增加(僅當 Q5 的輪胎、發動機和制動系統又出現上面沒有的型號時才需要相對應的增加,如果已經存在,則不需要增加),這裏就可以看出抽象工廠方法模式的一個弊端,就是類的徒增,如果工廠類過多,勢必會導致類文件增多,因此,在實際開發中一定要權衡慎用。
總結
- 抽象工廠方法模式的優點:
一個顯著的優點就是分離接口與實現,客戶端使用抽象工廠來創建需要的對象,而客戶端根本就不知道具體的實現邏輯,客戶端只是面向產品的接口編程而已,使其從具體的產品實現中解耦,同時基於接口與實現的分離,是抽象工廠方法模式在切換產品類時更加靈活、容易。
- 抽象工廠方法模式的缺點:
上面我們也有所提及,一是類文件的爆炸性增加,二是不太容易擴展新的產品類,因爲每當我們增加一個產品類就需要修改抽象工廠,那麼所有的具體工廠類均會被修改。