設計模式深入淺出(一)對象創建——工廠方法,抽象工廠

前言

好久沒寫博客了,之前AF的源碼解析也沒寫完,後續會補上。

回到設計模式。

說實話,本人寫的設計模式系列博客,也只是依照我目前淺薄的見解來寫的,並不能保證完全的正確,也希望大家有不同的想法,可以提出共同探討。

首先我們提到設計模式總是會有一種高大上的感覺,其實設計模式並不是那麼高冷,也許我們平常已在不覺總用到了若干設計模式,只是不知道其名字而已。

那麼什麼是設計模式呢?用通俗的話來講,就是用面向對象的思想來解決我們平日編程中所遇到的難以處理的情況,提高我們程序的健壯性與拓展性。

這裏說到了面向對象思想,其實不外乎那說到爛的三點:繼承,封裝,多態。基本上當我們第一天接觸面向對象思想的時候,就已經把這三點背的滾瓜爛熟。

但是要想真正理解這三點並熟練的運用,卻是需要多年的經驗的積累與不斷實踐。

而這23種設計模式,則是面向對象思想最好的體現,他們並不是憑空想象出來了,而是實踐編程經驗的總結,因此,每種設計模式,都是有實踐意義的,並且有適用的場合。

在GoF的《設計模式》一書中,將23種設計模式按照應用場景分爲了三大類:

  • 創建型模式(工廠方法,抽象工廠,builder,原型,單例):將對象創建出來
  • 結構型模式(適配器,橋接,組成,裝飾,外觀,享元,代理):在對象間建立松耦合以協作或訪問
  • 行爲模式(職責鏈,命令,解釋器,迭代器,中介者,備忘錄,觀察者,狀態,策略,模板,訪問者):對象是如何工作的

    但竊以爲,以上分類方式還是有些抽象,因此本系列博客將會以如下形式組織結構(參考《Objective-C編程之道》 Carlo Chung著):

  • 對象創建:工廠方法,抽象工廠,builder,原型,單例
  • 接口適配:適配器,橋接,外觀
  • 對象去耦:中介者,觀察者
  • 抽象集合:組合,迭代器
  • 行爲擴展:訪問者,裝飾,職責鏈
  • 算法封裝:模板,策略,命令
  • 性能與對象訪問:享元,代理(代理的懶加載方法可以提升性能,因此放在了一起,但這只是代理的一個應用而言)
  • 對象狀態:備忘錄,狀態

OK,先從第一個模式說起:

工廠方法

先說一個現實的例子:

在現實生活中,有許多手機廠商,蘋果,三星,華爲,小米等等。他們生產各自牌子的手機,外形,價格,配置都不盡相同。

但是,有沒有這種情況,當我從小米換到華爲手機,需要重新學習如何打電話呢?現實當然是沒有的,不可能每個手機廠商都自定一套自己操作手機打電話的方式。

好,這就說明,每個手機廠商,在生產手機時,都默認符合一種標準,這種標準對用戶來說是提供了一個統一的接口,讓用戶來操作手機。這種統一的標準的好處在於,當用戶更換手機型號,品牌時,不需要再重新學習如何操作手機

而這種統一的標準,轉換成面向對象編程語言就是統一的接口,而這種統一接口的實現方式,則是繼承(或OC中的protocol)。也就是說,所有手機生產廠家的手機,是繼承於統一父類的 : Super phone。

而不同廠商,爲了讓用戶方便的用上本廠生產的手機,必需要向用戶提供一個統一接口,用來生產手機,這個統一接口,在設計模式中稱作工廠方法(factory method)。我們這有涉及到了統一接口的問題,自然又想到了將所有手機廠商繼承於統一的父類:Super Phone Factory。Super factory 中定義了工廠方法:createNewPhone:用來生產產品——Super Phone

OK,這個手機廠商生產手機的問題,我們就可以抽象成如下類圖:
這裏寫圖片描述

那我們到這裏再通用些,我們不僅侷限於手機及手機廠商,我們將手機,抽象爲產品 Product,將手機廠商抽象爲創建者 Creator,那麼上圖被改造爲:
這裏寫圖片描述

好了,現在我們學習的第一個設計模式——工廠方法的UML圖已經出來了。上面所說的生產手機的例子,也就是工廠模式的通俗理解。

我們再回顧一下工廠模式的要點:

  1. 抽象了產品product,與生產者Creator。
  2. 用戶通過Creator的工廠方法,來獲取對應的產品。

工廠方法有什麼好處?回到我們上面的例子,當我們更換手機時,不需要重新學習手機的操作方法。也就是說,product的底層實現是對用戶透明的(注意,這裏的用戶指的是你代碼的使用者,比如與你協作的程序員或你自己,而不是APP的用戶)。對用戶層來說,他只需要知道抽象的prodcut類和Creator類即可,而不必關心底層的實現。

這樣在用戶層邏輯變得簡單而易於實現,不同產品的不同被封裝在了具體的product中,用戶永遠都是調用的抽象product的接口,具體的實現,則依賴於具體的product,這一部分是對用戶透明的。

僞代碼可能是這樣的:

// 抽象的Product與Creator
@interface SuperPhone:NSObject
- (void)makeCall;
@end
@interface SuperPhoneFactory:NSObject
- (SuperPhone *)createNewPhone;
@end

// 華爲工廠與華爲手機
@interface HuaWeiFactory:SuperPhoneFactory
- (SuperPhone *)createNewPhone;
@end
@implementation HuaWeiFactory
- (SuperPhone *)createNewPhone {
    return [HuaWeiPhone new];
}
@end

@interface HuaWeiPhone:SuperPhone
@end

// 蘋果工廠與iPhone
@interface AppleFactory:SuperPhoneFactory
- (SuperPhone *)createNewPhone;
@end
@implementation AppleFactory 
- (SuperPhone *)createNewPhone {
    return [iPhone new];
}
@end

@interface iPhone:SuperPhone
@end

// 這用MyPhoneFactory來對工廠方法的調用做一次封裝
NS_ENUM(NSInteger, PhoneFactory){
PhoneFacotryHuaWei = 1,
PhoneFactoryApple = 2,
};

@interface MyPhoneFactory:NSObject
- (instancetype)initWithPhoneFactory:(SuperPhoneFactory *)factory;
- (SuperPhone *)createMyNewPhone;
@end

@interface MyPhoneFactory()
@property(nonatomic, strong) SuperPhoneFactory *factory;
@end
@implementation MyPhoneFactory
- (instancetype)initWithPhoneFactory:(SuperPhoneFactory *)factory {
    if(self = [super init]) {
        _factory = factory;
        }
}
- (SuperPhone *)createMyNewPhone {
    return [self.factory createNewPhone];
}
}
@end

// 下面是用戶調用部分:
MyPhoneFactory *myPhoneFactory = [[MyPhoneFactory alloc] initWithFactory:[AppleFactory new]]; // 如果想更改手機型號,僅需init方法裏面的參數改爲其他factory即可,其餘代碼都不用變,就可正常使用

Persion *person = [Persion new];
person.phone = [myPhoneFactory createMyNewPhone];
[person.phone makeCall]; // 打電話

抽象工廠

說完了工廠模式,我們再進一步說下抽象工廠模式。

還是以上面的手機工廠的例子繼續。上面的手機工廠中,僅提供了一種產品:手機。但是現在手機廠商多聰明啊,僅僅有手機是不夠的,他們還會生產配套的耳機,數據線,手機套等更多的產品來獲取利潤。而耳機,數據線,手機套這些雖然大小,插口可能不同,但同樣是遵循統一的協議的。(不然我們還需要重新學習如何使用不同廠商的耳機?)

也就是說,我們的工廠現在不僅生產一種產品,而是會生產多種產品,並且這多種產品,也是各自遵循自己的統一標準。那麼,工廠模式的UML圖可擴展爲:
這裏寫圖片描述

好了,這就是抽象工廠的類圖。抽象工廠與工廠模區別在於:抽象工廠生產了一系列的產品,當更換抽象工廠時,會更換掉用戶所使用的一系列產品。而工廠模式僅支持一種產品。可以說,工廠模式是抽象工廠模式的一個特例。

Cocoa Touch框架中的工廠模式

工廠模式應該算是應用的比較多的模式了。在Cocoa Touch框架中,NSNumber類,其實就應用了工廠模式。
還記的如何初始化一個NSNumber類嗎?

 NSNumber *num1 = [NSNumber numberWithInteger:12];
 NSNumber *num2 = [NSNumber numberWithBool:YES];
 NSLog(@"%@ %@", [num1 class], [num2 class]);

打印出的結果爲:
這裏寫圖片描述

可以看到,NSNumber對於integer值和Bool值的實現,底層分別使用的NSCFNumber類和NSCFBoolean類。但Cocoa Touch框架暴露給我們的接口卻是統一的NSNumber類型的‘產品’。這讓我們在處理代碼時,不會爲了NSCFNumber 和NSCFBoolean兩種類型來寫兩套代碼。

還有另一個應用,也許比較隱祕,就是我們在自定義UITableViewCell的時候。
當我們要使用自定義的cell的時候,通常第一步是註冊我們自定義的cell,這一步類似於註冊不同的cell工廠:

 [_testTableView registerClass:[SWProductTableViewCell class] forCellReuseIdentifier:@"PRODUCT_CELL"];

並在UITableView的DataSource中,調用工廠方法:

dequeueReusableCellWithIdentifier:

返回對應的產品,自定義的cell。但對於UITableView來說,不論我們對cell做了多大更改,他始終是一種產品:UITableViewCell。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    SWProductTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PRODUCT_CELL"];
    SWProductItem *productItem = [[SWProductItem alloc] init];
    productItem.productName = @"AE-12";
    productItem.price = indexPath.row;
    productItem.productPictures = [NSArray new];
    cell.productItem = productItem;

    return cell;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章