Objective-C类族和工厂模式
本文转载至 http://www.cocoachina.com/ios/20141124/10296.html
相信大家都了解GoF的《Design Patterns》中提到的23种设计模式,其中将常见的设计模式分为三大类:创建型模式、行为型模式、结构型模式。而在《Clean Code》中也提到建造酒店的例子,系统中对象的构建和使用应当分离开,那么应该怎么构建对象更加整洁和符合使用场景就很重要。
在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。
那么,如下这样的判断就不行了:
1
2
3
4
|
id maybeAnArray = /* ... */ ; if ([maybeAnArray class] == [NSArray class]) { // Will never be hit } |
需要在适当的情况下选择使用isMemberOfClass和isKindOfClass。
本文到这就整理这么多,更多内容可参看:
《Effective Objective-C》第9条