1. 程式人生 > >工廠模式三部曲-抽象工廠模式

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

什麼是抽象工廠模式

簡單瞭解一下

按照慣例,我們先了解一下什麼是抽象工廠模式。抽象工廠模式和工廠方法模式很相似,但是抽象工廠模式將抽象發揮的更加極致,是三種工廠模式中最抽象的一種設計模式。抽象工廠模式,也叫做Kit模式,提供了建立一系列相關抽象子類的介面,而無需指定它們具體的型別。

抽象工廠模式中定義了抽象工廠類,抽象工廠類中定義了每個系列的抽象子類建立所需的方法,這些方法對應著不同型別的抽象子類例項化過程。每個工廠子類都對應著一個系列,工廠子類通過重寫這些方法來例項化當前系列的抽象子類。

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

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

我們上面講了系列的概念,這裡將會用一個例子來理解系列和抽象類的關係。假設現在需要用SqliteCoreData兩種不同的方式進行本地持久化,持久化的內容都是使用者資訊、搜尋資訊、設定資訊三部分。

就拿Sqlite持久化方式來說,Sqlite就是使用Sqlite資料庫持久化方式的系列,下面對應著使用者資訊、搜尋資訊、設定資訊三個型別,每個型別就是一個抽象類。除了Sqlite這種持久化方式外,還有CoreData這種持久化方式,這是兩個不同的持久化方式,所以屬於兩個不同的系列。

SqliteCoreData都代表著不同的系列,其下面都分別對應著使用者資訊、搜尋資訊、設定資訊三個型別的層級,在這種層級關係中,Sqlite的使用者資訊抽象子類對應著CoreData的使用者資訊抽象子類,這兩個抽象子類都屬於同一個型別,繼承自同一個抽象類,分別被不同系列的工廠子類建立。在抽象設計模式中,不同系列相同型別的抽象子類都是一一對應的

SqliteCoreData屬於不同的系列,所以是兩個不同的工廠子類,這兩個工廠子類具有相同的行為,就是使用者資訊、搜尋資訊、設定資訊三部分的資料持久化,這就是三種不同的持久化型別,也就是我們上面說的型別。這三個行為定義在抽象工廠類中,抽象工廠類中定義每個系列的抽象子類建立方法,SqliteCoreData繼承自抽象工廠類,並分別實現繼承過來的抽象子類建立方法。

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

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

在工廠方法模式中,工廠子類負責抽象子類的例項化,每個工廠子類對應著一個抽象子類,且具有唯一性。而在抽象工廠模式中,一個工廠子類代表一個系列,工廠子類根據當前系列對不同型別的抽象子類進行建立。工廠方法模式中工廠子類對應的是一個型別的抽象子類,抽象工廠模式對應的是一個系列的抽象子類

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

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

業務場景

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

iOS中比較常用的資料持久化方案,應該就包括SqliteCoreData了,可能Sqlite的靈活性使其更加受歡迎。業務就是需要用SqliteCoreData兩種不同的方式進行本地持久化,持久化的內容是使用者資訊、搜尋資訊、設定資訊三部分。

通過抽象工廠模式實現上面的需求,可以很方便的進行本地持久化方案的切換,下面的例子中將會演示一行程式碼切換資料持久化方案的例子。

UML類圖

我們根據上面的業務場景畫了一個UML類圖,下面類圖中為了讓大家看得更清晰,所以用不同顏色的線區分開了對應的類和功能

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


抽象工廠模式

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

普通方式程式碼實現

這裡程式碼實現按照上面舉的例子,程式碼結構也完全按照上面UML類圖中畫的結構,使整篇文章可以更加統一,更深刻的理解這個設計模式。

程式碼量比較多,但是為了更好的體現出抽象工廠模式,所以就全貼出來了。

首先建立兩個Model類,這兩個Model類並不屬於抽象工廠模式結構的一部分,只是為了更好的體現出面向模型開發。
@interface User : NSObject
@property (nonatomic, copy  ) NSString  *userName;
@property (nonatomic, assign) NSInteger userAge;
@end
@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切換,這樣業務中存在多個系列的需求,使用抽象工廠模式。如果比較簡單的建立單個型別的抽象子類,這種方式建議用簡單工廠模式或工廠方法模式。三種設計模式的選擇還是要看需求和專案複雜度,用得好的話可以給程式碼帶來極大的靈活性和擴充套件性

總結

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