工廠模式三部曲 - 抽象工廠模式

該文章屬於<簡書 — 劉小壯>原創,轉載請註明:

<簡書 — 劉小壯> http://www.jianshu.com/p/750b82fa6a62git


這是__工廠模式三部曲__中的最後一篇了,在這篇文章中將會講述__抽象工廠模式__,抽象工廠模式正如其名字同樣,很是抽象。可是抽象工廠模式的功能卻十分強大,對抽象工廠的利用也很是好。github

這篇文章中會像本系列第一篇同樣,給出普通實現方式和使用了反射機制的實現兩種代碼,而且會說明這兩種實現方式的區別。而且在文章的最後,會將這三種模式放在一塊兒,對這三種工廠模式進行總結。數據庫

本人理解可能不夠深入,這一系列文章中存在的問題,歡迎你們提出,謝謝!設計模式


博客配圖

什麼是抽象工廠模式

#####簡單瞭解一下 按照慣例,咱們先了解一下什麼是抽象工廠模式。抽象工廠模式和工廠方法模式很類似,可是抽象工廠模式將抽象發揮的更加極致,是三種工廠模式中最抽象的一種設計模式。抽象工廠模式,也叫作__Kit__模式,提供了建立一系列相關抽象子類的接口,而無需指定它們具體的類型。緩存

抽象工廠模式中定義了抽象工廠類,抽象工廠類中定義了每一個系列的抽象子類建立所需的方法,這些方法對應着不一樣類型的抽象子類實例化過程。每一個工廠子類都對應着一個系列,工廠子類經過重寫這些方法來實例化當前系列的抽象子類。架構

工廠方法模式中抽象子類都是基於同一個抽象類的,是同一個類型的抽象子類,例如加、減、乘、除都屬於運算類型。而__抽象工廠模式可能會有多個類型的抽象類,抽象子類分別繼承自對應類型的抽象類,相同類型的抽象子類都是屬於不一樣系列的__。框架

抽象工廠模式包含四部分:
  • 抽象工廠類:定義建立抽象子類的具體行爲,根據系列中不一樣類型的抽象子類可能會有多種行爲。
  • 工廠子類:繼承自抽象工廠類,根據當前抽象子類對應的系列,重寫父類定義的對應行爲。對應的抽象子類系列不一樣,行爲的實現方式也不一樣。
  • 抽象類:定義當前類型抽象子類的操做,子類繼承父類完成具體的操做。在抽象工廠模式中,可能會有多種抽象類的定義。
  • 抽象子類:根據當前類型繼承自對應的抽象類,並根據系列的不一樣重寫抽象類定義的實現。
我打算先講一個例子

咱們上面講了系列的概念,這裏將會用一個例子來理解系列和抽象類的關係。假設如今須要用SqliteCoreData兩種不一樣的方式進行本地持久化,持久化的內容都是用戶信息、搜索信息、設置信息三部分。學習

就拿Sqlite持久化方式來講,Sqlite就是使用Sqlite數據庫持久化方式的系列,下面對應着用戶信息、搜索信息、設置信息三個類型,每一個類型就是一個抽象類。除了Sqlite這種持久化方式外,還有CoreData這種持久化方式,這是兩個不一樣的持久化方式,因此屬於兩個不一樣的系列。優化

SqliteCoreData都表明着不一樣的系列,其下面都分別對應着用戶信息、搜索信息、設置信息三個類型的層級,在這種層級關係中,Sqlite的用戶信息抽象子類對應着CoreData的用戶信息抽象子類,這兩個抽象子類都屬於同一個類型,繼承自同一個抽象類,分別被不一樣系列的工廠子類建立。在抽象設計模式中,不一樣系列相同類型的抽象子類都是一一對應的atom

SqliteCoreData屬於不一樣的系列,因此是兩個不一樣的工廠子類,這兩個工廠子類具備相同的行爲,就是用戶信息、搜索信息、設置信息三部分的數據持久化,這就是三種不一樣的持久化類型,也就是咱們上面說的類型。這三個行爲定義在抽象工廠類中,抽象工廠類中定義每一個系列的抽象子類建立方法,SqliteCoreData繼承自抽象工廠類,並分別實現繼承過來的抽象子類建立方法。

經過上面的例子,咱們能夠清晰的理解工廠類、抽象類、系列三者之間的關係,理解這三者的關係能夠有助於咱們更好的理解抽象設計模式。

和工廠方法模式有什麼不一樣?

在工廠方法模式中,工廠子類負責抽象子類的實例化,每一個工廠子類對應着一個抽象子類,且具備惟一性。而在抽象工廠模式中,一個工廠子類表明一個系列,工廠子類根據當前系列對不一樣類型的抽象子類進行建立。工廠方法模式中工廠子類對應的是一個類型的抽象子類,抽象工廠模式對應的是一個系列的抽象子類

工廠方法模式一個工廠子類對應一個抽象子類的設計,會有很大的浪費,產生了過多的類。而__抽象工廠模式更好的利用了工廠子類__,使每一個工廠子類對應着一個系列的抽象子類,這種設計很是適用於兩個具備相同結構關係,可是分屬於不一樣系列的系列之間的切換。

總之就是,工廠方法模式是針對單個類型的抽象類,而抽象工廠模式是針對具備相同結構的一系列類型的抽象類


業務場景

在上面講到了數據持久化的例子,咱們的業務場景也根據上面的例子提出。

iOS中比較經常使用的數據持久化方案,應該就包括SqliteCoreData了,可能Sqlite的靈活性使其更加受歡迎。業務就是須要用SqliteCoreData兩種不一樣的方式進行本地持久化,持久化的內容是用戶信息、搜索信息、設置信息三部分。

經過抽象工廠模式實現上面的需求,能夠很方便的進行本地持久化方案的切換,下面的例子中將會演示一行代碼切換數據持久化方案的例子。

UML類圖

咱們根據上面的業務場景畫了一個__UML__類圖,下面類圖中爲了讓你們看得更清晰,因此__用不一樣顏色的線區分開了對應的類和功能__。

下面的黑色箭頭是抽象子類和抽象類的繼承關係;紅色是用戶工廠子類對應的抽象子類;黃色是搜索工廠子類對應的抽象子類;綠色是設置工廠子類對應的抽象子類。

抽象工廠模式

在這個__UML__類圖中,咱們能夠清晰的看出,以前工廠方法模式的工廠子類對應的是單一類型的抽象子類,上面__抽象工廠模式的工廠子類對應的是同一系列多個類型的抽象子類__,更好的利用了工廠子類,適合更加複雜的業務需求。抽象工廠類的方法定義也和工廠方法模式不太同樣,因爲工廠方法模式只建立一個抽象子類,因此直接用的類方法定義,抽象方法模式可能會建立多個類型的抽象子類,因此用的實例方法定義。

普通方式代碼實現

這裏代碼實現按照上面舉的例子,代碼結構也徹底按照上面__UML__類圖中畫的結構,使整篇文章能夠更加統一,更深入的理解這個設計模式。

代碼量比較多,可是爲了更好的體現出抽象工廠模式,因此就全貼出來了。

首先建立兩個Model類,這兩個Model類並不屬於抽象工廠模式結構的一部分,只是爲了更好的體現出面向模型開發。
[@interface](https://my.oschina.net/u/996807) User : NSObject
[@property](https://my.oschina.net/property) (nonatomic, copy  ) NSString  *userName;
[@property](https://my.oschina.net/property) (nonatomic, assign) NSInteger userAge;
[@end](https://my.oschina.net/u/567204)
@implementation User
@end

@interface City : NSObject
@property (nonatomic, copy) NSString *cityName;
@property (nonatomic, copy) NSString *cityCode;
@end
@implementation City
@end
用戶信息系列相關類
@interface UserInfo : NSObject
- (void)setUserName:(User *)name;
@end
@implementation UserInfo
- (void)setUserName:(User *)name {}
@end

@interface SqliteUserInfo : UserInfo
@end
@implementation SqliteUserInfo
- (void)setUserName:(User *)name {
    NSLog(@"這裏編寫數據庫持久化方案");
}
@end

@interface CoreDataUserInfo : UserInfo
@end
@implementation CoreDataUserInfo
- (void)setUserName:(User *)name {
    NSLog(@"這裏編寫CoreData持久化方案");
}
@end
搜索信息系列相關類
@interface SearchInfo : NSObject
- (void)setSearchCity:(City *)city;
@end
@implementation SearchInfo
- (void)setSearchCity:(City *)city {}
@end

@interface SqliteSearchInfo : SearchInfo
@end
@implementation SqliteSearchInfo
- (void)setSearchCity:(City *)city {
    NSLog(@"這裏編寫數據庫持久化方案");
}
@end

@interface CoreDataSearchInfo : SearchInfo
@end
@implementation CoreDataSearchInfo
- (void)setSearchCity:(City *)city {
    NSLog(@"這裏編寫CoreData持久化方案");
}
@end
設置信息系列相關類
@interface SettingsInfo : NSObject
- (void)resetAllSettings;
@end
@implementation SettingsInfo
- (void)resetAllSettings {}
@end

@interface SqliteSettingsInfo : SettingsInfo
@end
@implementation SqliteSettingsInfo
- (void)resetAllSettings {
    NSLog(@"重置數據庫設置信息");
}
@end

@interface CoreDataSettingsInfo : SettingsInfo
@end
@implementation CoreDataSettingsInfo
- (void)resetAllSettings {
    NSLog(@"重置CoreData設置信息");
}
@end
工廠抽象相關類
@interface Factory : NSObject
- (UserInfo *)CreateUserInfo;
- (SearchInfo *)CreateSearchInfo;
- (SettingsInfo *)CreateSettingsInfo;
@end
@implementation Factory
- (UserInfo *)CreateUserInfo {
    return nil;
}
- (SearchInfo *)CreateSearchInfo {
    return nil;
}
- (SettingsInfo *)CreateSettingsInfo {
    return nil;
}
@end

@interface SqliteFactory : Factory
@end
@implementation SqliteFactory
- (UserInfo *)CreateUserInfo {
    return [SqliteUserInfo new];
}
- (SearchInfo *)CreateSearchInfo {
    return [SqliteSearchInfo new];
}
- (SettingsInfo *)CreateSettingsInfo {
    return [SqliteSettingsInfo new];
}
@end

@interface CoreDataFactory : Factory
@end
@implementation CoreDataFactory
- (UserInfo *)CreateUserInfo {
    return [CoreDataUserInfo new];
}
- (SearchInfo *)CreateSearchInfo {
    return [CoreDataSearchInfo new];
}
- (SettingsInfo *)CreateSettingsInfo {
    return [CoreDataSettingsInfo new];
}
@end
外界使用
- (void)viewDidLoad {
    User *user = [User new];
    City *city = [City new];

    Factory *factory = [SqliteFactory new];
    UserInfo *userInfo = [factory CreateUserInfo];
    SearchInfo *searchInfo = [factory CreateSearchInfo];
    SettingsInfo *settingsInfo = [factory CreateSettingsInfo];

    [userInfo setUserName:user];
    [searchInfo setSearchCity:city];
    [settingsInfo resetAllSettings];
}

到此爲止咱們就編寫完抽象工廠設計模式的代碼了,上面抽象工廠模式的示例中定義了三個類型的抽象類UserInfoSearchInfoSettingsInfo,抽象子類分別繼承不一樣類型的抽象類,並實現不一樣系列的持久化代碼。這三個類型的抽象類中定義了兩種不一樣的數據持久化方案,分別是Sqlite存儲和CoreData存儲,這就是兩種數據持久化系列,分別用SqliteFactoryCoreDataFactory表示這兩個系列。

代碼中定義了抽象工廠類Factory類,並定義了三個抽象接口用來實例化不一樣類型的抽象子類,兩個工廠子類SqliteFactoryCoreDataFactory繼承自抽象工廠類,內部分別實現了兩種不一樣系列的抽象子類實例化,例如SqliteFactory中會實例化關於Sqlite存儲方式的抽象子類,並經過抽象工廠類中定義的抽象接口返回給外界使用。

統一控制工廠子類的切換

這三種工廠設計模式中除了簡單工廠模式,工廠方法模式和抽象工廠模式都須要外界實例化不一樣的工廠子類,這種在外界實例化工廠子類的代碼可能會出如今多個地方,而在不少業務需求中都須要咱們統一切換某個功能,從代碼上來講就是切換工廠子類,怎樣能夠作到統一切換工廠子類的需求呢?

就拿上面的持久化方案的例子來講,咱們定義了兩種持久化方案,經過SqliteFactoryCoreDataFactory工廠子類來建立不一樣的持久化方案。假設如今咱們項目比較龐大,代碼量比較多,而且在多個地方用到了SqliteFactory工廠子類,如今需求是切換爲CoreDataFactory的持久化方案,咱們應該怎樣快速的切換持久化方案?

其實咱們能夠經過typedef定義別名的方式切換工廠子類,在其餘地方只須要使用咱們typedef定義的別名就能夠,例以下面代碼就能夠作到修改一處typedef定義,就修改了整個項目的持久化方案。

仍是按照上面的抽象工廠模式的代碼,這裏只寫出外界使用的代碼部分
typedef SqliteFactory SaveFactory; //定義的工廠子類別名

@implementation TestTableViewController
- (void)viewDidLoad {
    User *user = [User new];
    City *city = [City new];

    Factory *factory = [SaveFactory new];
    UserInfo *userInfo = [factory CreateUserInfo];
    SearchInfo *searchInfo = [factory CreateSearchInfo];
    SettingsInfo *settingsInfo = [factory CreateSettingsInfo];

    [userInfo setUserName:user];
    [searchInfo setSearchCity:city];
    [settingsInfo resetAllSettings];
}

從上面的代碼能夠看到,咱們定義了一個SaveFactory的工廠子類別名,下面直接經過這個別名進行的工廠子類的實例化。由於若是存儲相同的內容,項目中只會出現一種持久化方案,因此咱們只須要修改typedef的定義,就能夠__切換整個項目的持久化方案__。

配合反射機制優化代碼

對於抽象工廠模式的反射機制,實現方式和以前的簡單工廠模式不太同樣,我__採用的是預編譯指令加常量字符串類名反射的方式實現的__。別的很少說,先上代碼來看看,我這裏貼出了主要代碼,其餘同樣的地方我就不往外貼了,不浪費你們時間。

仍是按照上面的業務場景,我定義了兩種同名不一樣值的字符串常量,這些__常量字符串對應的就是抽象子類的類名__,一個條件分支就是一個系列的抽象子類,經過預編譯指令#if來進行不一樣系列的抽象子類間的__統一切換__,定義了SAVE_DATA_MODE_SQLITE宏定義來控制系列的切換。這種定義方式能夠更方便的進行不一樣系列間的切換,從使用上來看很是像咱們用預編譯指令替換了以前的工廠子類,實際上從代碼的角度上來講這種方式對系列間的切換更加統一和方便。

#define SAVE_DATA_MODE_SQLITE 1

#if SAVE_DATA_MODE_SQLITE
static NSString * const kUserInfoClass     = @"SqliteUserInfo";
static NSString * const kSearchInfoClass   = @"SqliteSearchInfo";
static NSString * const kSettingsInfoClass = @"SqliteSettingsInfo";
#else
static NSString * const kUserInfoClass     = @"CoreDataUserInfo";
static NSString * const kSearchInfoClass   = @"CoreDataSearchInfo";
static NSString * const kSettingsInfoClass = @"CoreDataSettingsInfo";
#endif

下面是工廠類的定義,使用反射機制的抽象工廠模式刨除了工廠子類,只用一個工廠類來進行操做子類的實例化,這種方式和以前的簡單工廠模式很是類似。不一樣的是以前的簡單工廠模式只須要初始化一個類型的抽象子類,而__抽象工廠模式須要初始化多個類型的抽象子類__。

因爲咱們採用了反射機制,而且__由預編譯指令進行系列間的切換__,因此這裏就直接使用類方法了,哪裏用就直接實例化抽象子類便可,不存在工廠子類之間的選擇問題了。

@interface Factory : NSObject
+ (UserInfo *)CreateUserInfo;
+ (SearchInfo *)CreateSearchInfo;
+ (SettingsInfo *)CreateSettingsInfo;
@end

@implementation Factory
+ (UserInfo *)CreateUserInfo {
    return [[NSClassFromString(kUserInfoClass) alloc] init];
}
+ (SearchInfo *)CreateSearchInfo {
    return [[NSClassFromString(kSearchInfoClass) alloc] init];
}
+ (SettingsInfo *)CreateSettingsInfo {
    return [[NSClassFromString(kSettingsInfoClass) alloc] init];
}
@end

經過這種方式進行不一樣系列的切換,只須要修改一個宏定義的值便可,也就是SAVE_DATA_MODE_SQLITE後面的__1__切換爲__0__的步驟,這種方式是__符合咱們開放封閉原則的__。之後每一個系列增長新的類型後,只須要將新增長的兩個類名對應添加在預編譯指令中,在工廠方法中擴展一個實例化新類型的方法便可。這種方式對擴展是開放的,對修改是關閉的

對於上面的示例代碼的編寫須要注意一下,按照蘋果的命名規範,常量的做用域若是隻在一個類中,前面就用小寫__k__修飾,若是修飾的常量在其餘類中用到,也就是.h文件中用extern修飾的常量,不須要用小寫__k__修飾。咱們在蘋果的不少官方代碼和Kit中也能夠看到相同的定義,宏定義也是相同的規則。(extern修飾的常量在.m中不要用static修飾)

項目中若是用到任何預編譯指令,在修改從新運行前,必定要clear一下,清除緩存,不然會由於緩存致使__bug__。

抽象工廠模式的優缺點

優勢

抽象工廠模式正如其名字同樣,理解起來很是抽象,正是由於這種抽象,使得抽象工廠模式很是強大和靈活,比其餘兩種工廠設計模式要強大不少。抽象工廠模式能夠建立多個系列,而且每一個系列抽象子類一一對應,這種強大的功能是其餘兩種工廠模式都不能實現的。

經過抽象工廠模式統一控制多個系列的抽象子類,能夠用多個系列的抽象子類完成一些複雜的需求。例如咱們文中說到的本地持久化方案的切換,最後經過咱們的不斷優化,作到__只須要修改一個預編譯指令的參數便可切換整個數據持久化方案__,這是其餘工廠模式所不能完成的。

抽象工廠模式延續了工廠模式的優勢,外界接觸不到任何類型的抽象子類,而只須要知道不一樣類型的抽象類便可,抽象子類的建立過程都在工廠子類中。這種設計方式充分的利用了面嚮對象語言中的多態特性,使靈活性大大提高。並且__抽象工廠模式是很是符合開放封閉原則的,對擴展的開放以及對修改的封閉都完美支持__。

缺點

抽象工廠模式帶來的缺點也是顯而易見的,最明顯的缺點就是模式比較龐大,因此須要在適合的業務場景使用這種模式,不然會拔苗助長。


工廠模式三部曲總結

示例

到目前爲止,工廠模式三部曲中的三種工廠模式都已經講完了,在這裏咱們將__簡單回顧和總結一下這三種設計模式__。首先,我將根據三種工廠模式畫三張一樣需求的__UML__類圖,看完這三張類圖你們就對三種工廠模式清晰明瞭了。

需求就以如今比較火的樂視系列的樂視TV、樂視手機,小米系列的小米TV、小米手機來做爲需求,這三張圖主要體現工廠模式的總體架構。

簡單工廠模式

工廠方法模式

抽象工廠模式

從這三張圖中能夠看出,簡單工廠模式和工廠方法模式對應的是同一類型的操做結構,在當前例子中就是手機類型,由於只有一個類型,因此__尚未系列的概念__。在需求不太複雜,而且不須要多個系列間的切換時,能夠考慮使用這兩種設計模式。

以前的業務只有手機一種類型,在業務複雜以後出現了一個新類型的產品-電視,這時候工廠類就須要增長一種類型。因爲需求更加複雜,這時候就__出現了系列的概念(以前工廠方法模式中類型單一,因此不須要系列的概念)__,樂視系列和小米系列,工廠子類變成了每一個工廠子類對應一個系列的設計,每一個系列中對應不一樣類型的產品。

抽象工廠模式對應多個類型的操做結構,分屬於不一樣的系列。這種結構比較適合複雜的業務需求,例如文中將的Sqlite數據庫和CoreData兩種存儲方式的切換,經過抽象工廠模式就很是好實現。

#####工廠模式總結 在這三種設計模式中都有一個共同的特色,就是繼承自抽象類的抽象子類或工廠子類,都必須對抽象類定義的方法給出對應的實現(能夠相同,也能夠不一樣),這種模式才叫作工廠模式。工廠模式的核心就是抽象和多態,抽象子類繼承自抽象類,對抽象類中定義的方法和屬性給出不一樣的實現方式,經過多態的方式進行方法實現和調用,構成了工廠模式的核心。

在工廠類中對開放封閉原則有着完美的體現,對擴展的開放以及對修改的封閉。例如最抽象的抽象工廠模式,抽象工廠模式中增長新的系列,直接擴展一個工廠子類及對應的抽象子類,對整個模式框架不會帶來其餘影響。若是增長一個新的類型,建立新的類型對應的類,並對整個抽象工廠類及其子類進行方法擴展。

在外界使用抽象子類的功能時,不須要知道任何關於抽象子類的特徵,抽象子類也不會出如今外界,__外界只須要和抽象類打交道__就能夠。工廠模式將抽象子類的建立和實現分離,具體的建立操做由工廠類來進行,抽象子類只須要關注業務便可,外界不須要知道抽象子類實例化的過程。這種方式很是靈活並易於擴展,並且在大型項目中尤其明顯,能夠很好的避免代碼量過多的問題。

對於這三種工廠模式的選擇,我建議若是是像Sqlite數據庫和CoreData切換,這樣__業務中存在多個系列的需求__,使用抽象工廠模式。若是比較簡單的建立單個類型的抽象子類,這種方式建議用簡單工廠模式或工廠方法模式。三種設計模式的選擇仍是要看需求和項目複雜度,用得好的話能夠給代碼帶來極大的靈活性和擴展性

總結

設計模式主要是一種思想方面的東西,沒有任何一種設計模式是萬能的,並適應於各類業務場景的設計模式。因此在不一樣的地方使用對應的設計模式,或者說根據業務須要設計一種適合當前業務場景的設計模式,這纔是最理想的設計模式用法。


前段時間寫了關於工廠模式的系列文章,這系列文章理解起來比較難懂。應廣大讀者的須要,這段時間專門給這系列文章補了Demo

Demo只是來輔助讀者更好的理解文章中的內容,應該博客結合Demo一塊兒學習,只看Demo仍是不能理解更深層的原理Demo中代碼都會有註釋,各位能夠打斷點跟着Demo執行流程走一遍,看看各個階段變量的值。

Demo地址劉小壯的Github

相關文章
相關標籤/搜索