//聯系人:石虎QQ: 1224614774昵稱:嗡嘛呢叭咪哄
一、Objective-C類族和工廠模式
在iOS的系統類庫中也有一種方式使得開發者不必關注類中具體的存儲實現,但可以根據不同需求場景創建出合適的對象來。比如Foudation中的NSArray、UIkit中的UIButton。
本文結合幾種工廠設計模式的原理,對Objective-C類族的概念做一下簡要的整理。
0.三種工廠
其實除了《Design Patterns》中提到的Factory Method和Abstract Factory,常提到的還有另一種工廠模式Simple Factory。
在簡單工廠中,產品有一個統一的interface,而可以有不同implementation,同時有一個(通常是僅有一個)工廠對象。需要產品的時候,工廠會根據已有條件做switch,選擇一種產品實現,構建一個實例出來。這種設計將產品的抽象和實現分離開來,可以針對統一的產品接口進行通信,而不必特意關注具體實現的不同。對于產品類別的擴充也是可以的,但每增加一個產品,都需要修改工廠的邏輯,有一定維護成本。
工廠方法更進一步,將工廠也抽象出來,進行接口、實現分離。這樣具體工廠和具體產品可以對應著同時擴充,而不需要修改現有邏輯。當然,使用者也許在不同場景要在一定程度上自己對應的工廠選擇(這個總要有人知道,不可避免)。
抽象工廠相對于工廠方法主要是對整個產品類的體系進行了橫向擴充,構成一個更為完整的系統。
1. Objective-C的類族(Class Cluster)
做iOS開發的朋友們一定用過NSNumber的numberWith…方法。但大家有可能都不知道NSNumber這樣的方法調用返回的不是NSNumber類本身的對象,這正是Objective-C類族的微妙之處。
如上圖所示,Number的概念很大。而實際上NSNumber實際上是有很多隱藏的子類的,而我們通過NSNumber的numberWith…方法得到的對象正是其子類的對象,但對于使用者幾乎可以不必知道這一點,只要知道它是一個NSNumber對象就OK了。
“Simple Concept and Simple Interface”,這正是蘋果設計類族的初衷,也是類族的優點所在。可以想象我們要用整數作為參數拿到一個NSNumber對象和一個布爾值參數拿到的NSNumber對象是不同的,這略微有些類似于switch邏輯(雖然是通過不同的方法),根據不同的條件提供不同的子類對象,而這一切都集中聲明在公共接口類NSNumber中。我們很容易聯想到上面提到的Simple Factory(簡單工廠)設計模式。
沒錯,與簡單工廠類似,類族的一個缺點也顯現出來,那就是已有的類族不好擴展。比如你想NSNumber再多支持一種情況,這個恐怕很難。好在這些系統的類庫已經將大部分可能都做進去了,考慮得比較完善,通常你只是去用就可以了。
2.類族的子類擴展
了解了類族的概念,我們在實際開發當中也可以采用其方式,利用其優點。上面提到對已有類族進行子類擴展是很難的,但這不代表NSNumber、NSArray等類就沒法繼承了。他們還是可以有自定義的子類的。
既然要做類族的子類,就要做到:
·以公共“抽象”類為父類,比如NSNumber、NSArray等,而非其子類
·提供自定義存儲
·重寫(覆蓋)父類所有初始化方法
·重寫父類中“原始”方法
其中第二點最重要,系統的類族通常在父類中只是提供了各種方法聲明,而自身并不提供存儲,所以要自定義子類一定要提供自己的存儲,一般情況下這也是自定義子類的意義所在。
重寫初始化方法,要遵從Objective-C初始化鏈的規范。
而重寫“原始”方法,這個要說一下。按照蘋果的文檔,和指定初始化方法形式類似,這些類里面的眾多方法可以分為兩類,“原始”方法和“衍生”方法。“原始”方法定義了這個類及對象的最基本行為,而“衍生”方法則基于這些“原始”方法進行更復雜邏輯的包裝。所以,重寫了“原始”方法,“衍生”方法也自然效果就不同了。
除了自定義子類外,蘋果官方更建議開發者用組合的方式對類族類進行包裝。
3.對象所屬類的判斷
有人會問,如果我沒有特殊需求,不需要寫NSArray、NSNumber的子類,是不是了解類族就沒有多大意義了。這里記一下,通過了解類族概念,我們至少知道了,通過NSNumber得到的對象,不一定是(基本上就不會是)NSNumber類本身的對象。
可以試驗下,通過[NSNumber numberWithInt:2]和[NSNumber numberWithBool:YES]得到的對象對應的類,一個是__NSCFNumber,另一個是__NSCFBoolean。
那么,如下這樣的判斷就不行了:
idmaybeAnArray=;
if([maybeAnArrayclass]==[NSArrayclass]){
//Willneverbehit
}
需要在適當的情況下選擇使用isMemberOfClass和isKindOfClass。
“類族”(class cluster)是一種很有用的模式(pattern),可以隱藏“抽象基類”(abstract base class)背后的實現細節。Objective-C的系統框架中普遍使用此模式,比如iOS的用戶界面框架(user interface framework)UIKit中就有一個名為UIButton的類,想創建按鈕,需要調用下面這個“類方法”(class method):
+(UIButton*)buttonWithType:(UIButtonType)type;
該方法所返回的對象,其類型取決于傳入的按鈕類型(button type),然而,不管返回什么類型的對象,它們都繼承自同一個基類:UIButton。這么做的意義在于:UIButton類的使用者無須關心創建出來的按鈕具體屬于哪個子類,也不用考慮按鈕的繪制方式等實現細節,使用者只需明白如何創建按鈕,如何設置像“標題”(title)這樣的屬性,如何增加觸摸動作的目標對象等問題就好。
創建類族
現在舉例來演示如何創建類族,假設有一個處理雇員的類,每個雇員都有“名字”和“薪水”這兩個屬性,管理者可以命令其執行日常工作,但是,各種雇員的工作內容卻不同,經理在帶領雇員做項目時,無須關心每個人如何完成其工作,僅需指示其開工即可。
首先要定義抽象基類:
typedef NS_ENUM(NSUInteger,EOCEmployeeType){
EOCEmployeeTypeDeveloper,
EOCEmployeeTypeDesigner,
EOCEmployeeTypeFinance,
};
@interface EOCEmployee : NSObject
@property(copy)NSString *name;
@property NSUInteger salary;
// Helper for creating Employee objects
+(EOCEmployee*)employeeWithType:(EOCEmployeeType)type;
// Make Employees do their respective day's work
-(void)doADaysWork;
@end
@implementation EOCEmployee
+(EOCEmployee*)employeeWithType:(EOCEmployeeType)type {
switch(type){
case EOCEmployeeTypeDeveloper:
return[EOCEmployeeDeveloper new];
break;
case EOCEmployeeTypeDesigner:
return[EOCEmployeeDesigner new];
break;
case EOCEmployeeTypeFinance:
return[EOCEmployeeFinance new];
break;
}
}
-(void)doADaysWork {
// Subclasses implement this.
}
@end
每個“實體子類”(concrete subclass)都從基類繼承而來,例如:
@interface EOCEmployeeDeveloper : EOCEmployee
@end
@implementation EOCEmployeeDeveloper
-(void)doADaysWork {
[self writeCode];
}
@end
在本例中,基類實現了一個“類方法”,該方法根據待創建的雇員類別分配好對應的雇員類實例,這種“工廠模式”(Factory pattern)是創建類族的辦法之一。
可惜Objective-C這門語言沒辦法指明某個基類是“抽象的”(abstract),于是,開發者通常會在文檔中寫明類的用法,這種情況下,基類接口一般都沒有名為init的成員方法,這暗示該類的實例也許不應該由用戶直接創建。
Cocoa里的類族
系統框架中有許多類族,大部分collection類都是類族,如NSArray,下面這種代碼:
id maybeAnArray =;
if([maybeAnArray class]==[NSArray class]){
// Will never be hit
}
其中的if語句永遠不可能為真。[maybeAnArray class]所返回的類絕不可能是NSArray類本身,因為由NSArray的初始化方法所返回的那個實例其類型是隱藏在類族公共接口(public facade)后面的某個內部類型(internal type)。
要判斷出某個實例所屬的類是否位于類族之中,應該使用類型信息查詢方法(introspection method),不要直接檢測兩個“類對象”是否等同,而應該采用下列代碼:
id maybeAnArray =;
if([maybeAnArray isKindOfClass:[NSArray class]]){
// Will be hit
}
謝謝!!!