抽象工廠模式
介紹
工廠方法模式通過引入工廠等級(jí)結(jié)構(gòu)攘轩,解決了簡(jiǎn)單工廠模式中工廠類職責(zé)太重的問題构哺,但由于工廠方法模式中的每個(gè)工廠只生產(chǎn)一類產(chǎn)品懂盐,可能會(huì)導(dǎo)致系統(tǒng)中存在大量的工廠類冶忱,勢(shì)必會(huì)增加系統(tǒng)的開銷袱箱。此時(shí)庆揪,我們可以考慮將一些相關(guān)的產(chǎn)品組成一個(gè)“產(chǎn)品族”式曲,由同一個(gè)工廠來統(tǒng)一生產(chǎn),這就是抽象工廠模式的基本思想嚷硫。
在工廠方法模式中具體工廠負(fù)責(zé)生產(chǎn)具體的產(chǎn)品检访,每一個(gè)具體工廠對(duì)應(yīng)一種具體產(chǎn)品,工廠方法具有唯一性仔掸,一般情況下脆贵,一個(gè)具體工廠中只有一個(gè)或者一組重載的工廠方法。但是有時(shí)候我們希望一個(gè)工廠可以提供多個(gè)產(chǎn)品對(duì)象起暮,而不是單一的產(chǎn)品對(duì)象卖氨,如一個(gè)電器工廠,它可以生產(chǎn)電視機(jī)负懦、電冰箱筒捺、空調(diào)等多種電器,而不是只生產(chǎn)某一種電器纸厉。為了更好地理解抽象工廠模式系吭,我們先引入兩個(gè)概念:
- (1) 產(chǎn)品等級(jí)結(jié)構(gòu):產(chǎn)品等級(jí)結(jié)構(gòu)即產(chǎn)品的繼承結(jié)構(gòu),如一個(gè)抽象類是電視機(jī)颗品,其子類有海爾電視機(jī)肯尺、海信電視機(jī)沃缘、TCL電視機(jī),則抽象電視機(jī)與具體品牌的電視機(jī)之間構(gòu)成了一個(gè)產(chǎn)品等級(jí)結(jié)構(gòu)则吟,抽象電視機(jī)是父類槐臀,而具體品牌的電視機(jī)是其子類。
- (2) 產(chǎn)品族:在抽象工廠模式中氓仲,產(chǎn)品族是指由同一個(gè)工廠生產(chǎn)的水慨,位于不同產(chǎn)品等級(jí)結(jié)構(gòu)中的一組產(chǎn)品,如海爾電器工廠生產(chǎn)的海爾電視機(jī)敬扛、海爾電冰箱晰洒,海爾電視機(jī)位于電視機(jī)產(chǎn)品等級(jí)結(jié)構(gòu)中,海爾電冰箱位于電冰箱產(chǎn)品等級(jí)結(jié)構(gòu)中啥箭,海爾電視機(jī)欢顷、海爾電冰箱構(gòu)成了一個(gè)產(chǎn)品族。
產(chǎn)品等級(jí)結(jié)構(gòu)與產(chǎn)品族示意圖:
不同顏色的多個(gè)正方形捉蚤、圓形和橢圓形分別構(gòu)成了三個(gè)不同的產(chǎn)品等級(jí)結(jié)構(gòu)抬驴,而相同顏色的正方形、圓形和橢圓形構(gòu)成了一個(gè)產(chǎn)品族缆巧,每一個(gè)形狀對(duì)象都位于某個(gè)產(chǎn)品族布持,并屬于某個(gè)產(chǎn)品等級(jí)結(jié)構(gòu)。
當(dāng)系統(tǒng)所提供的工廠生產(chǎn)的具體產(chǎn)品并不是一個(gè)簡(jiǎn)單的對(duì)象陕悬,而是多個(gè)位于不同產(chǎn)品等級(jí)結(jié)構(gòu)题暖、屬于不同類型的具體產(chǎn)品時(shí)就可以使用抽象工廠模式。抽象工廠模式是所有形式的工廠模式中最為抽象和最具一般性的一種形式捉超。抽象工廠模式與工廠方法模式最大的區(qū)別在于胧卤,工廠方法模式針對(duì)的是一個(gè)產(chǎn)品等級(jí)結(jié)構(gòu),而抽象工廠模式需要面對(duì)多個(gè)產(chǎn)品等級(jí)結(jié)構(gòu)拼岳,一個(gè)工廠等級(jí)結(jié)構(gòu)可以負(fù)責(zé)多個(gè)不同產(chǎn)品等級(jí)結(jié)構(gòu)中的產(chǎn)品對(duì)象的創(chuàng)建枝誊。當(dāng)一個(gè)工廠等級(jí)結(jié)構(gòu)可以創(chuàng)建出分屬于不同產(chǎn)品等級(jí)結(jié)構(gòu)的一個(gè)產(chǎn)品族中的所有對(duì)象時(shí),抽象工廠模式比工廠方法模式更為簡(jiǎn)單惜纸、更有效率叶撒。
抽象工廠模式示意圖:
每一個(gè)具體工廠可以生產(chǎn)屬于一個(gè)產(chǎn)品族的所有產(chǎn)品,例如生產(chǎn)顏色相同的正方形耐版、圓形和橢圓形祠够,所生產(chǎn)的產(chǎn)品又位于不同的產(chǎn)品等級(jí)結(jié)構(gòu)中。如果使用工廠方法模式粪牲,圖所示結(jié)構(gòu)需要提供15個(gè)具體工廠古瓤,而使用抽象工廠模式只需要提供5個(gè)具體工廠,極大減少了系統(tǒng)中類的個(gè)數(shù)腺阳。
定義
抽象工廠模式為創(chuàng)建一組對(duì)象提供了一種解決方案落君。與工廠方法模式相比滴须,抽象工廠模式中的具體工廠不只是創(chuàng)建一種產(chǎn)品,它負(fù)責(zé)創(chuàng)建一族產(chǎn)品叽奥。
提供一個(gè)創(chuàng)建一系列相關(guān)或相互依賴對(duì)象的接口,而無須指定它們具體的類痛侍。抽象工廠模式又稱為Kit模式朝氓,它是一種對(duì)象創(chuàng)建型模式。
UML類圖
角色
- AbstractFactory(抽象工廠):它聲明了一組用于創(chuàng)建一族產(chǎn)品的方法主届,每一個(gè)方法對(duì)應(yīng)一種產(chǎn)品赵哲。
- ConcreteFactory(具體工廠):它實(shí)現(xiàn)了在抽象工廠中聲明的創(chuàng)建產(chǎn)品的方法,生成一組具體產(chǎn)品君丁,這些產(chǎn)品構(gòu)成了一個(gè)產(chǎn)品族枫夺,每一個(gè)產(chǎn)品都位于某個(gè)產(chǎn)品等級(jí)結(jié)構(gòu)中。
- AbstractProduct(抽象產(chǎn)品):它為每種產(chǎn)品聲明接口绘闷,在抽象產(chǎn)品中聲明了產(chǎn)品所具有的業(yè)務(wù)方法橡庞。
- ConcreteProduct(具體產(chǎn)品):它定義具體工廠生產(chǎn)的具體產(chǎn)品對(duì)象,實(shí)現(xiàn)抽象產(chǎn)品接口中聲明的業(yè)務(wù)方法印蔗。
demo代碼
在抽象工廠中聲明了多個(gè)工廠方法扒最,用于創(chuàng)建不同類型的產(chǎn)品,抽象工廠可以是接口华嘹,也可以是抽象類或者具體類吧趣,其典型代碼如下所示:
abstract class AbstractFactory {
public abstract AbstractProductA createProductA(); //工廠方法一
public abstract AbstractProductB createProductB(); //工廠方法二
……
}
具體工廠實(shí)現(xiàn)了抽象工廠,每一個(gè)具體的工廠方法可以返回一個(gè)特定的產(chǎn)品對(duì)象耙厚,而同一個(gè)具體工廠所創(chuàng)建的產(chǎn)品對(duì)象構(gòu)成了一個(gè)產(chǎn)品族强挫。對(duì)于每一個(gè)具體工廠類,其典型代碼如下所示:
class ConcreteFactory1 extends AbstractFactory {
//工廠方法一
public AbstractProductA createProductA() {
return new ConcreteProductA1();
}
//工廠方法二
public AbstractProductB createProductB() {
return new ConcreteProductB1();
}
……
}
與工廠方法模式一樣薛躬,抽象工廠模式也可為每一種產(chǎn)品提供一組重載的工廠方法俯渤,以不同的方式對(duì)產(chǎn)品對(duì)象進(jìn)行創(chuàng)建。
抽象工廠與工廠方法的比較
相同點(diǎn)
- 創(chuàng)建對(duì)象而不讓客戶知曉返回了什么確切的具體對(duì)象型宝。
不同點(diǎn)
抽象工廠 | 工廠方法 |
---|---|
通過對(duì)象組合創(chuàng)建抽象產(chǎn)品 | 通過類繼承創(chuàng)建抽象產(chǎn)品 |
創(chuàng)建多系列產(chǎn)品 | 創(chuàng)建一種產(chǎn)品 |
必須修改父類的接口才能支持新產(chǎn)品 | 子類化創(chuàng)建者重載工廠方法以創(chuàng)建新產(chǎn)品 |
Cocoa框架中使用抽象工廠
1.NSNumber
創(chuàng)建各種類型的NSNumber對(duì)象
NSNumber *boolNumber = [NSNumber numberWithBool:YES]
NSNumber *IntNumber = [NSNumber numberWithInt:1]
NSNumber *floatNumber = [NSNumber numberWithFloat:1.0]
...
上面的方法返回的類型是NSCFBoolean和NSCFNumber稠诲。而且__NSCFNumber和__NSCFBoolean很明顯是一個(gè)私有類。上面出現(xiàn)的這種現(xiàn)象在Foundation Framework中是很常見的一種情況诡曙,我們稱作為類簇設(shè)計(jì)模式臀叙。那為什么要這樣做呢?以NSNumber為例价卤,我們知道NSNumber可以存儲(chǔ)很多類型的數(shù)據(jù)劝萤,如int、Float慎璧、Double等等床嫌,具體支持哪些數(shù)據(jù)類型可以到NSNumber的頭文件中查看跨释,具體支持的類型如下:
char、unsigned char厌处、short鳖谈、unsigned short、int阔涉、unsigned int缆娃、long、unsigned long瑰排、long long贯要、unsigned long long、float椭住、double崇渗、BOOL、NSInteger京郑、NSUInteger
一般情況下實(shí)現(xiàn)類似的效果一種方式是把NSNumber作為基類宅广,然后分別去實(shí)現(xiàn)各自的子類,如下圖所示:
但是一旦需要實(shí)現(xiàn)的子類多起來之后就會(huì)發(fā)現(xiàn)這樣需要繼承的子類太多些举,比如如果要仿NSNumber需要寫這樣十多個(gè)類乘碑。這對(duì)使用者來說顯然不夠方便,得記住這么多類金拒。如果使用類簇兽肤,問題就變得簡(jiǎn)單了,把Number作為抽象基類绪抛,子類各自實(shí)現(xiàn)存取方式资铡,然后在基類中定義多個(gè)初始化方式。NSNumber初始化偽代碼如下:
- (id)initWithBool
{
return [[__NSCFBoolean alloc]init];
}
- (id)initWithLong
{
return [[__NSCFNumber alloc]init];
}
//...
xxxValue方法(intValue)
NSNumber為它的子類也提供一系列公有的接口幢码,intValue笤休、boolValue等
[boolNumber intValue]; // 返回1
[charNumber boolValue]; // 返回YES
boolNumber在內(nèi)部保持布爾值YES,但仍然實(shí)現(xiàn)了公有的intValue方法症副,返回反映其內(nèi)部布爾值的適當(dāng)整數(shù)值店雅。
類簇
類簇是基礎(chǔ)框架中一種常見的設(shè)計(jì)模式,基于抽象工廠模式的思想贞铣,它將若干相關(guān)的私有具體工廠子類集合到一個(gè)公有的抽象超類之下闹啦。它是接口簡(jiǎn)單性和擴(kuò)展性的權(quán)衡體現(xiàn),在我們完全不知情的情況下辕坝,偷偷隱藏了很多具體的實(shí)現(xiàn)類窍奋,只暴露出簡(jiǎn)單的接口。例如,“數(shù)”包含了各種數(shù)值類型的完整集合琳袄,如字符江场、整數(shù)、浮點(diǎn)數(shù)和雙精度數(shù)窖逗。這些數(shù)值類型是“數(shù)”的子集址否。所以NSNumber自然成為這些數(shù)子類型的超類型。NSNumber有一系列公有API碎紊,定義了各種類型的數(shù)所公有的行為佑附。客戶端使用時(shí)無需知道NSNumber實(shí)例的具體類型矮慕。
其他實(shí)現(xiàn)類簇的基礎(chǔ)類有NSData、NSArray啄骇、NSDictionary痴鳄、NSString
以抽象工廠模式的角度看NSNumber類簇
NSNumber是抽象工廠,定義一系列創(chuàng)建“數(shù)”(產(chǎn)品)的方法缸夹,如intValue痪寻、boolValue等。這些類型就是產(chǎn)品族(簇)虽惭。
NSCFBoolean和NSCFNumber是具體工廠類橡类,重載了NSNumber中聲明的公有工廠方法以生產(chǎn)產(chǎn)品。
2.NSArray類簇
NSLog(@"%@", objc_getClass("NSArray")); // NSArray
NSLog(@"%@", NSStringFromClass([NSArray class])); // NSArray
NSLog(@"%@", objc_getClass("NSMutableArray")); // NSMutableArray
NSLog(@"%@", NSStringFromClass([NSMutableArray class])); // NSMutableArray
NSArray *array1 = @[@"111", @"222", @"3333", @"4444"];
NSLog(@"array1: %@", object_getClass(array1)); // __NSArrayI
NSArray *array2 = [@[@"111", @"222", @"3333", @"4444"] mutableCopy];
NSLog(@"array2: %@", object_getClass(array2)); // __NSArrayM
id obj4 = [NSArray alloc];//注意: 得到的并不是NSArray類型的對(duì)象 // __NSPlaceholderArray
NSLog(@"obj4: %@", object_getClass(obj4));
NSArray *array5 = [obj4 init]; // __NSArray0 數(shù)組沒有元素
NSLog(@"array5: %@", object_getClass(array5));
NSMutableArray *obj6 = [NSMutableArray alloc]; // __NSPlaceholderArray
NSLog(@"array6: %@", object_getClass(obj6));
NSMutableArray *array7 = [obj6 init];
NSLog(@"array7: %@", object_getClass(array7)); // __NSArrayM
我們使用NSArray不同情況得到的一個(gè)對(duì)象芽唇,其實(shí)根本不是NSArray類的對(duì)象顾画,而是NSArray內(nèi)部所管理的各種子類(__NSArray0、__NSArrayI匆笤、__NSArrayM)的對(duì)象 研侣,那么這樣的作用 使用一個(gè)統(tǒng)一的入口類,來屏蔽內(nèi)部各種各樣的內(nèi)部類.
3.使用類簇實(shí)現(xiàn)系統(tǒng)適配
#import "MyTool.h"
// 其他私有的不同系統(tǒng)版本的實(shí)現(xiàn)子類
// 我們可以把它寫在 .m 中炮捧,而不必寫成單獨(dú)的 類文件
#import "MyTool_iOS6.h"
#import "MyTool_iOS7.h"
#import "MyTool_iOS8.h"
@implementation MyTool
// 判斷當(dāng)前系統(tǒng)版本庶诡,選擇對(duì)應(yīng)系統(tǒng)版本下的實(shí)現(xiàn)類
+ (instancetype)alloc {
if (self == [MyTool class]) {
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) {
//iOS6
return [MyTool_iOS6 alloc];
} else if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1 && floor(NSFoundationVersionNumber) < NSFoundationVersionNumber_iOS_8_0) {
//iOS7
return [MyTool_iOS7 alloc];
} else if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_7_1){
//iOS8及以上
return [MyTool_iOS8 alloc];
}
}
return [super alloc];
}
- (void)doWork { /* 空實(shí)現(xiàn),由具體子類去實(shí)現(xiàn) */ }
@end
總結(jié)
優(yōu)點(diǎn)
- 抽象工廠模式隔離了具體類的生成咆课,使得客戶并不需要知道什么被創(chuàng)建末誓。由于這種隔離,更換一個(gè)具體工廠就變得相對(duì)容易书蚪,所有的具體工廠都實(shí)現(xiàn)了抽象工廠中定義的那些公共接口喇澡,因此只需改變具體工廠的實(shí)例,就可以在某種程度上改變整個(gè)軟件系統(tǒng)的行為殊校。
- 當(dāng)一個(gè)產(chǎn)品族中的多個(gè)對(duì)象被設(shè)計(jì)成一起工作時(shí)撩幽,它能夠保證客戶端始終只使用同一個(gè)產(chǎn)品族中的對(duì)象。
- 增加新的產(chǎn)品族很方便,無須修改已有系統(tǒng)窜醉,符合“開閉原則”宪萄。
缺點(diǎn)
- 在抽象工廠模式中,增加新的產(chǎn)品族很方便榨惰,但是增加新的產(chǎn)品等級(jí)結(jié)構(gòu)很麻煩拜英,抽象工廠模式的這種性質(zhì)稱為“開閉原則”的傾斜性。
- 增加新的產(chǎn)品等級(jí)結(jié)構(gòu)麻煩琅催,需要對(duì)原有系統(tǒng)進(jìn)行較大的修改居凶,甚至需要修改抽象層代碼,這顯然會(huì)帶來較大的不便藤抡,違背了“開閉原則”侠碧。
適用場(chǎng)景
- 一個(gè)系統(tǒng)不應(yīng)當(dāng)依賴于產(chǎn)品類實(shí)例如何被創(chuàng)建、組合和表達(dá)的細(xì)節(jié)缠黍,這對(duì)于所有類型的工廠模式都是很重要的弄兜,用戶無須關(guān)心對(duì)象的創(chuàng)建過程,將對(duì)象的創(chuàng)建和使用解耦瓷式。
- 系統(tǒng)中有多于一個(gè)的產(chǎn)品族替饿,而每次只使用其中某一產(chǎn)品族∶车洌可以通過配置文件等方式來使得用戶可以動(dòng)態(tài)改變產(chǎn)品族视卢,也可以很方便地增加新的產(chǎn)品族。
- 屬于同一個(gè)產(chǎn)品族的產(chǎn)品將在一起使用廊驼,這一約束必須在系統(tǒng)的設(shè)計(jì)中體現(xiàn)出來据过。同一個(gè)產(chǎn)品族中的產(chǎn)品可以是沒有任何關(guān)系的對(duì)象,但是它們都具有一些共同的約束妒挎,如同一操作系統(tǒng)下的按鈕和文本框蝶俱,按鈕與文本框之間沒有直接關(guān)系,但它們都是屬于某一操作系統(tǒng)的饥漫,此時(shí)具有一個(gè)共同的約束條件:操作系統(tǒng)的類型榨呆。
- 產(chǎn)品等級(jí)結(jié)構(gòu)穩(wěn)定,設(shè)計(jì)完成之后庸队,不會(huì)向系統(tǒng)中增加新的產(chǎn)品等級(jí)結(jié)構(gòu)或者刪除已有的產(chǎn)品等級(jí)結(jié)構(gòu)积蜻。