類簇可以說是Objective-C語言中比較重要的設(shè)計,Apple在官方文檔中用一篇文章來介紹這個概念,盡管文章點到為止,并沒有深入到內(nèi)部機(jī)制,但是也用了詳細(xì)的例子來說明類簇的設(shè)計是多么優(yōu)秀。Apple在文檔中稱類簇是基于抽象工廠模式來設(shè)計的,如果你對抽象工廠的定義不清晰,可能會問,抽象工廠是什么?如果你對抽象工廠的定義清晰,有可能會問,類簇的設(shè)計真的是基于抽象工廠模式嗎?本文站在設(shè)計模式的源頭,對類簇進(jìn)行挖掘解讀。
先別急,我的文章需要延續(xù)一貫的個人風(fēng)格,有足夠的準(zhǔn)備,我們先來理解設(shè)計模式中的工廠模式
工廠模式
工廠模式一共有三種,簡單工廠模式,工廠方法模式,抽象工廠模式。接下來我們依次通過圖文并茂的描述進(jìn)行理解
簡單工廠模式
先來看看簡單工廠的UML圖(回歸經(jīng)典,用的Java語言)

我們將簡單工廠模式分為幾個部分來解讀:
-
產(chǎn)品:
- 定義了一個抽象的產(chǎn)品類
- 有兩個具體產(chǎn)品類繼承了該抽象類
-
工廠:
-
creatProduct(String):Product方法:參數(shù)為String類型,返回值為Product類型 - 偽代碼實現(xiàn):
if(type == "A") { return new ProductA(); } else { return new ProductB(); }
-
-
使用:
- 偽代碼示例:
Product product = factory.creatProduct("A"); product.operation();
- 偽代碼示例:
可以發(fā)現(xiàn),主要的邏輯代碼寫在了工廠的方法中,那么如果產(chǎn)品種類增多,我們就需要去修改工廠的方法?;貞浺幌略O(shè)計模式六大原則之一的開放-封閉原則,開放指的是面向擴(kuò)展開放,封閉指的是面向修改封閉。再回到簡單工廠模式來看,是不是違背了原則呢。所以在實際應(yīng)用中,正如它的名字,它僅僅在一些簡單場景使用。
工廠方法模式
同樣地,我們先來看看工廠方法模式的UML圖

同樣地,將其分為幾個部分來解讀:
-
產(chǎn)品:
- 定義了一個抽象的產(chǎn)品類
- 有兩個具體產(chǎn)品類繼承了該抽象類
-
工廠:
- 定義了一個抽象的工廠類
- 有兩個具體工廠類繼承了該抽象類
-
FactoryA類中的+ creatProduct():Product方法偽代碼:return new ProductA(); -
FactoryB類中的creatProduct():Product方法偽代碼:return new ProductB();
-
使用:
- 偽代碼演示:
// 創(chuàng)建產(chǎn)品A Factory factoryA = new FactoryA(); Product productA = factoryA.creatProduct(); // 創(chuàng)建產(chǎn)品B Factory factoryB = new FactoryB(); Product productB = factoryB.creatProduct();
- 偽代碼演示:
看完了簡單工廠模式和工廠方法模式,通過對比可以發(fā)現(xiàn),工廠方法模式在簡單工廠模式的基礎(chǔ)之上,踐行了開放-封閉原則,創(chuàng)建產(chǎn)品不再通過傳入?yún)?shù)判斷應(yīng)該生成哪個具體產(chǎn)品,而是將工廠創(chuàng)建產(chǎn)品的任務(wù)放在了子類去做。當(dāng)增加了新的產(chǎn)品時,我們只需要創(chuàng)建新的子類,實現(xiàn)方法,不需要對抽象類進(jìn)行修改。
抽象工廠模式
老樣子,來看看抽象工廠的UML圖

分成三部分來解讀抽象工廠模式:
-
產(chǎn)品:
- 有兩個抽象的產(chǎn)品類
- 有兩個具體產(chǎn)品類分別繼承了各自的抽象類
-
工廠:
- 定義了一個抽象的工廠類
- 有兩個具體工廠類繼承了該抽象類
- 創(chuàng)建產(chǎn)品的方法,以
Factory1類示例:-
creatProductA():ProductA方法的偽代碼:return new ProductA1(); -
creatProductB():ProductB方法的偽代碼:return new ProductB1();
-
-
使用:
- 偽代碼演示:
// 型號為1的產(chǎn)品 Factory factory1 = new Factory1(); ProductA productA1 = factory1.creatProductA(); ProductB productB1 = factory1.creatProductB(); // 型號為2的產(chǎn)品 Factory factory2 = new Factory2(); ProductA productA2 = factory2.creatProductA(); ProductB productB2 = factory2.creatProductB();
- 偽代碼演示:
可以發(fā)現(xiàn),抽象工廠模式其實就是在工廠方法模式的基礎(chǔ)上增加了產(chǎn)品的種類,產(chǎn)品的抽象類從一個變成了多個,那么這樣一來,產(chǎn)品就構(gòu)成了一個體系。這個體系有兩個關(guān)鍵詞,產(chǎn)品簇和產(chǎn)品等級結(jié)構(gòu),為了理解這兩個關(guān)鍵字,我們先來看個實際的例子:

- 產(chǎn)品等級結(jié)構(gòu):等級結(jié)構(gòu)就是繼承結(jié)構(gòu),如上圖中手機(jī)和電腦,有一個抽象類,然后具體的產(chǎn)品繼承這個抽象類
- 產(chǎn)品簇:上圖中iphoneX和MacBook Pro都是產(chǎn)于同一個公司,蘋果公司。而小米MIX和小米筆記本Pro也是產(chǎn)于同一家公司。即產(chǎn)品簇的概念是,產(chǎn)于同一家工廠,位于不同產(chǎn)品等級結(jié)構(gòu)中的一組產(chǎn)品。
有了以上的準(zhǔn)備知識之后,我們來看看Objective-C中的類簇設(shè)計
類簇
在官方文檔中,蘋果僅僅告訴我們類簇是基于抽象工廠模式來設(shè)計的
Class clusters are based on the Abstract Factory design pattern.
文檔中通過對比是否使用類簇來設(shè)計接口,告訴了我們類簇的設(shè)計師優(yōu)秀的。它能產(chǎn)生簡單的概念,同時能夠使接口簡化。那么類簇是什么呢?
類簇的定義
Class clusters group a number of private concrete subclasses under a public abstract superclass. The grouping of classes in this way simplifies the publicly visible architecture of an object-oriented framework without reducing its functional richness.
蘋果告訴我們,類簇匯集了若干私有具體子類隱藏在共有的抽象父類之下,通過這種方式,簡化了面向?qū)ο罂蚣苤泄部梢姷募軜?gòu),同時并沒有減少框架功能的豐富性。
舉個例子
來看下NSNumber,它是Objective-C中關(guān)于數(shù)據(jù)類型的封裝類

Users of this hierarchy see only one public class,
Number, so how is it possible to allocate instances of the proper subclass? The answer is in the way the abstract superclass handles instantiation.
對于這個層級的使用者,僅僅能看到一個共有類,那就是NSNumber,所以要分配內(nèi)存創(chuàng)建合適的子類對象,又怎么可能呢?答案就是這個抽象父類對如何分配進(jìn)行了處理。
看到這里,我心里油然而生一個大大的問號,這都哪跟哪,說好的抽象工廠呢,連個工廠都沒有,產(chǎn)品自己生產(chǎn)了自己。如果大家也有這個問題,那么帶著這個問題,繼續(xù)往下看
類簇的使用
官方文檔中舉例:
NSNumber *aChar = [NSNumber numberWithChar:’a’];
NSNumber *anInt = [NSNumber numberWithInt:1];
NSNumber *aFloat = [NSNumber numberWithFloat:1.0];
NSNumber *aDouble = [NSNumber numberWithDouble:1.0];
The abstract superclass in a class cluster must declare methods for creating instances of its private subclasses. It’s the superclass’s responsibility to dispense an object of the proper subclass based on the creation method that you invoke—you don’t, and can’t, choose the class of the instance.
抽象父類務(wù)必需要定義創(chuàng)建私有子類的方法,這是抽象父類的職責(zé)。抽象父類需要基于大家調(diào)用的創(chuàng)建方法去分配內(nèi)存到合適的子類,同時使用者不需要也不能選擇對象的類型,統(tǒng)一使用NSNumber來管理。
到這里,官方介紹介紹,疑惑并沒有解決,大家可以能有這種感覺,除了沒有工廠之外,看起來就和簡單工廠模式沒兩樣。
我們應(yīng)該如何理解?
首先,我們嘗試著先找到工廠在哪里,官方文檔中舉例是通過類方法的,可以隱藏了一些細(xì)節(jié),那么我們換做對象方法來試試
id number_alloc = [NSNumber alloc];
id number_init_int = [number_alloc initWithInt:3];
id number_init_bool = [number_alloc initWithBool:YES];
id number_init_char = [number_alloc initWithChar:'c'];
id number_init_float = [number_alloc initWithFloat:1.1];
再來看看類型
(lldb) p number_alloc
(NSPlaceholderNumber *) $0 = 0x0000600000017e30
(lldb) p number_init_int
(__NSCFNumber *) $1 = 0xb000000000000032 (int)3
(lldb) p number_init_bool
(__NSCFBoolean *) $2 = 0x00000001038815e0
(lldb) p number_init_char
(__NSCFNumber *) $3 = 0xb000000000000630 (char)99
(lldb) p number_init_float
(__NSCFNumber *) $4 = 0x00006080000323e0 (float)1.100000
看來是有些收獲的,在alloc的后,得到的對象類型是NSPlaceholderNumber(回想之前解讀官方文檔時,蘋果多次提到了allocate(分配))接著進(jìn)行了init,得到的結(jié)果主要有兩種類型,__NSCFNumber和__NSCFBoolean。那么初步的UML架構(gòu)就可以建立了:

是簡單工廠嗎?
從這個UML圖來看,貌似和簡單工廠模式差不多呢,但是細(xì)心觀察,這里并沒有像簡單工廠模式一樣違背了開放-封閉原則。在工廠類NSPlaceholderNumber中,提供了創(chuàng)建不種類產(chǎn)品的方法。這點和抽象工廠很像。那和抽象工廠不一樣的地方呢?
和抽象工廠的對比
仔細(xì)地想下,NSNumber的UML架構(gòu)就仿佛是將抽象工廠模式中以工廠為單位分割出來的小單位?;氐叫∶缀吞O果的那個例子,就好像其中的一個產(chǎn)品簇,例如只有蘋果公司以及它的iphoneX和MacBook Pro。嘿嘿,看到這里,NSNumber的抽象工廠設(shè)計的神秘面紗悄悄地揭開了。
為何這么設(shè)計?
剛剛提到了,NSNumber目前只是產(chǎn)品簇中的一條產(chǎn)品線。那么如果以后產(chǎn)生了其他類型的產(chǎn)品線,蘋果不需要修改之前的代碼,只需要添加另外的工廠和另外的產(chǎn)品線,將開放-封閉原則發(fā)揮地玲離盡致。
參考文獻(xiàn)
- 官方文檔:Class Cluster
- 《head first design patterns》