类簇可以说是Objective-C
语言中比较重要的设计,Apple
在官方文档中用一篇文章来介绍这个概念,尽管文章点到为止,并没有深入到内部机制,但是也用了详细的例子来说明类簇的设计是多么优秀。Apple
在文档中称类簇是基于抽象工厂模式来设计的,如果你对抽象工厂的定义不清晰,可能会问,抽象工厂是什么?如果你对抽象工厂的定义清晰,有可能会问,类簇的设计真的是基于抽象工厂模式吗?本文站在设计模式的源头,对类簇进行挖掘解读。
先别急,我的文章需要延续一贯的个人风格,有足够的准备,我们先来理解设计模式中的工厂模式
工厂模式
工厂模式一共有三种,简单工厂模式,工厂方法模式,抽象工厂模式。接下来我们依次通过图文并茂的描述进行理解
简单工厂模式
先来看看简单工厂的UML
图(回归经典,用的Java
语言)
我们将简单工厂模式分为几个部分来解读:
-
产品:
- 定义了一个抽象的产品类
- 有两个具体产品类继承了该抽象类
-
工厂:
-
creatProduct(String):Product
方法:参数为String
类型,返回值为Product
类型 - 伪代码实现:
if(type == "A") { return new ProductA(); } else { return new ProductB(); }
-
-
使用:
- 伪代码示例:
Product product = factory.creatProduct("A"); product.operation();
- 伪代码示例:
可以发现,主要的逻辑代码写在了工厂的方法中,那么如果产品种类增多,我们就需要去修改工厂的方法。回忆一下设计模式六大原则之一的开放-封闭原则,开放指的是面向扩展开放,封闭指的是面向修改封闭。再回到简单工厂模式来看,是不是违背了原则呢。所以在实际应用中,正如它的名字,它仅仅在一些简单场景使用。
工厂方法模式
同样地,我们先来看看工厂方法模式的UML
图
同样地,将其分为几个部分来解读:
-
产品:
- 定义了一个抽象的产品类
- 有两个具体产品类继承了该抽象类
-
工厂:
- 定义了一个抽象的工厂类
- 有两个具体工厂类继承了该抽象类
-
FactoryA
类中的+ creatProduct():Product
方法伪代码:return new ProductA();
-
FactoryB
类中的creatProduct():Product
方法伪代码:return new ProductB();
-
使用:
- 伪代码演示:
// 创建产品A Factory factoryA = new FactoryA(); Product productA = factoryA.creatProduct(); // 创建产品B Factory factoryB = new FactoryB(); Product productB = factoryB.creatProduct();
- 伪代码演示:
看完了简单工厂模式和工厂方法模式,通过对比可以发现,工厂方法模式在简单工厂模式的基础之上,践行了开放-封闭原则,创建产品不再通过传入参数判断应该生成哪个具体产品,而是将工厂创建产品的任务放在了子类去做。当增加了新的产品时,我们只需要创建新的子类,实现方法,不需要对抽象类进行修改。
抽象工厂模式
老样子,来看看抽象工厂的UML
图
分成三部分来解读抽象工厂模式:
-
产品:
- 有两个抽象的产品类
- 有两个具体产品类分别继承了各自的抽象类
-
工厂:
- 定义了一个抽象的工厂类
- 有两个具体工厂类继承了该抽象类
- 创建产品的方法,以
Factory1
类示例:-
creatProductA():ProductA
方法的伪代码:return new ProductA1();
-
creatProductB():ProductB
方法的伪代码:return new ProductB1();
-
-
使用:
- 伪代码演示:
// 型号为1的产品 Factory factory1 = new Factory1(); ProductA productA1 = factory1.creatProductA(); ProductB productB1 = factory1.creatProductB(); // 型号为2的产品 Factory factory2 = new Factory2(); ProductA productA2 = factory2.creatProductA(); ProductB productB2 = factory2.creatProductB();
- 伪代码演示:
可以发现,抽象工厂模式其实就是在工厂方法模式的基础上增加了产品的种类,产品的抽象类从一个变成了多个,那么这样一来,产品就构成了一个体系。这个体系有两个关键词,产品簇和产品等级结构,为了理解这两个关键字,我们先来看个实际的例子:
- 产品等级结构:等级结构就是继承结构,如上图中手机和电脑,有一个抽象类,然后具体的产品继承这个抽象类
- 产品簇:上图中iphoneX和MacBook Pro都是产于同一个公司,苹果公司。而小米MIX和小米笔记本Pro也是产于同一家公司。即产品簇的概念是,产于同一家工厂,位于不同产品等级结构中的一组产品。
有了以上的准备知识之后,我们来看看Objective-C
中的类簇设计
类簇
在官方文档中,苹果仅仅告诉我们类簇是基于抽象工厂模式来设计的
Class clusters are based on the Abstract Factory design pattern.
文档中通过对比是否使用类簇来设计接口,告诉了我们类簇的设计师优秀的。它能产生简单的概念,同时能够使接口简化。那么类簇是什么呢?
类簇的定义
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.
苹果告诉我们,类簇汇集了若干私有具体子类隐藏在共有的抽象父类之下,通过这种方式,简化了面向对象框架中公共可见的架构,同时并没有减少框架功能的丰富性。
举个例子
来看下NSNumber
,它是Objective-C
中关于数据类型的封装类
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
,所以要分配内存创建合适的子类对象,又怎么可能呢?答案就是这个抽象父类对如何分配进行了处理。
看到这里,我心里油然而生一个大大的问号,这都哪跟哪,说好的抽象工厂呢,连个工厂都没有,产品自己生产了自己。如果大家也有这个问题,那么带着这个问题,继续往下看
类簇的使用
官方文档中举例:
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.
抽象父类务必需要定义创建私有子类的方法,这是抽象父类的职责。抽象父类需要基于大家调用的创建方法去分配内存到合适的子类,同时使用者不需要也不能选择对象的类型,统一使用NSNumber
来管理。
到这里,官方介绍介绍,疑惑并没有解决,大家可以能有这种感觉,除了没有工厂之外,看起来就和简单工厂模式没两样。
我们应该如何理解?
首先,我们尝试着先找到工厂在哪里,官方文档中举例是通过类方法的,可以隐藏了一些细节,那么我们换做对象方法来试试
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(分配))接着进行了init
,得到的结果主要有两种类型,__NSCFNumber
和__NSCFBoolean
。那么初步的UML
架构就可以建立了:
是简单工厂吗?
从这个UML
图来看,貌似和简单工厂模式差不多呢,但是细心观察,这里并没有像简单工厂模式一样违背了开放-封闭原则。在工厂类NSPlaceholderNumber
中,提供了创建不种类产品的方法。这点和抽象工厂很像。那和抽象工厂不一样的地方呢?
和抽象工厂的对比
仔细地想下,NSNumber
的UML
架构就仿佛是将抽象工厂模式中以工厂为单位分割出来的小单位。回到小米和苹果的那个例子,就好像其中的一个产品簇,例如只有苹果公司以及它的iphoneX和MacBook Pro。嘿嘿,看到这里,NSNumber
的抽象工厂设计的神秘面纱悄悄地揭开了。
为何这么设计?
刚刚提到了,NSNumber
目前只是产品簇中的一条产品线。那么如果以后产生了其他类型的产品线,苹果不需要修改之前的代码,只需要添加另外的工厂和另外的产品线,将开放-封闭原则发挥地玲离尽致。
参考文献
- 官方文档:Class Cluster
- 《head first design patterns》