第二部分 对 象 创 建
第3章 原 型
原型摸式是一种非常简单的设计模式。使用原型实例指定创建对象的种类,并通过复制这个原型创建新的对象。
在以下情形,会考虑使用原型模式。
- 需要创建的对象应独立于其类型与创建方式
- 要实例化的类是在运行时决定的。
- 不想要与产品层次相对应的工厂层次。
- 不同类的实例间的差异仅是状态的若干组合。因此复制相应数量的原型比手工实例化更加方便。
- 类不容易创建,比如每个组件可把其他组件作为子节点的组合对象。复制已有的组合对象并对副本进行修改会更加容易。
此模式的最低限度是生成对象的真实副本,以用作用一个环境下其他相关事物的基础(原型)。
浅复制与深复制
如果对象有个指针型成员变量指向内存中的某个资源,那么如何复制这个对象呢?你会只是复制指针的值传给副本的新对象吗?指针只是存储内存中资源地址的占位符。在复制操作中,如果只是将指针复制给新对象,那么底层的资源实际上仍然由两个实例在共享。所以只复制了指针值而不是实际资源,这称为浅复制。
深复制,是指不仅复制指针值,还复制指针所指向的资源。
使用Cocoa Touch框架中的对象复制
Cocoa Touch 框架为NSObject的派生类提供了实现深复制的协议。NSObject的子类需要实现NSCopying协议及其方法—— (id)copyWithZone:(NSZone *)zone。 NSObject有一个实例方法叫做(id)copy。默认的copy方法调用[self copyWithZone:nil].对于采纳了NSCopying协议的子类,而要实现这个方法,否则将引发异常。iOS中这个方法保持新的副本对象,然后将其返回。此方法的调用者要负责释放返回的对象。
总结:原型模式是用于对象创建的非常简单的模式。下一章会介绍另一个对象创建模式,它并不使用copy方法创建同一类型的对象,而是使用一个决定生成何种对象的方法。
第4章 工 厂 方 法
工厂方法也称为虚构造器。它适用于这种情况:一个类无法预期需要生成哪个类的对象,想让其子类来指定所生成的对象。
工厂方法模式:定义创建对象的接口,让子类决定实例化哪一个类。工厂方法使得一个类的实例化延迟到其子类。
何时使用工厂方法:
- 编译时无法准确预期要创建的对象的类;
- 类想让其子类决定在运行时创建什么;
- 类有若干辅助类为其子类,而你想将返回哪个子类这一信息局部化。
使用这一模式的最低限度是,工厂方法能给予类在变更返回哪一种对象这一点上更多的灵活性。使用这一架构的一个常见例子是Cocoa Touch框架中的NSNumber.尽管可以使用常见的alloc init两步法创建NSNumber实例,但这没什么用,除非使用预先定义的类工厂方法来创建有意义的实例。工厂方法模式对框架设计者特别有用。
为何这是创建对象的安全方法
与直接创建新的具体对象相比,使用工厂方法创建对象可算作一种最佳做法。工厂方法模式让客户程序可以要求有工厂方法创建的对象拥有一组共同的行为。所以往类层次结构中引入新的具体产品并不需要修改客户端代码,因为返回的任何具体对象的接口都跟客户端一直在用的从前的接口相同。
在Cocoa Touch框架中应用工厂方法
就NSNumber而言,没有可在别处生成的其他“数字生成器”,而是在类级别打包提供了方法以达到类似的效果。它们称作类工厂法。
总结,工厂方法是面向对象软件设计中应用非常普遍的设计模式。工厂方法从代码中消除了对应用程序特有类的耦合。代码只需要处理prodect抽象接口。所以同一代码得以复用,在应用程序中与用户定义的任何ConcreteProcuct类一起工作。我们使用工厂方法模式协助实现了TouchPainter应用程序,使其支持多种CanvasView以供用用户选择。
在下一章,将接触到工厂方法模式密切相关另一种对象创建模式。
第5章 抽 象 工 厂
抽象工厂: 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
两个工厂模式的对比
抽象工厂: 通过对象组合创建抽象产品;创建多系列产品;必须修改父类的接口才能支持新的产品。
工厂方法:通过类继承创建抽象产品;创建一种产品; 子类化创建者并重载工厂方法以创建新产品。
把抽象工厂应用到TouchPainter应用程序
软件设计的黄金法则:变动需要抽象
当现有的抽象工厂需要支持新产品时,需要向父类添加相应的新工厂方法。
在Cocoa Touch 框架中使用抽象工厂
抽象工厂模式常见于Cocoa Touch框架。有很多基础类采用了这一模式。特别常见的一个就是天天在用的NSNumber。
总结:抽象工厂模式是一种极为常见的设计模式。它是最基本的,因为它可以涉及许多类型的对象创建。一系列相关类的好的模式,应该作为一种抽象,不为客户端所见。抽象工厂可以顺畅地提供这种抽象,而不暴露创建过程中任何不必要的细节或所创建对象的确切类型。下一章要讨论创建抽象对象的另一种方法——生成器模式
第6章 生 成 器
如果能够把构建过程分解为客户-指导者-生成器(client-director-builder)的关系,那么过程将更容易管理与复用。针对此类关系的设计模式称为生成器。
builder是一个抽象接口,声明了一个builderPart方法,该builder方法由ConcreteBuilder实现,以构造实际产品Product。ConcreteBuilder有个getResult方法,向客户端返回构造完毕的Product. Director定义了一个construct方法,命令builder的实例去buildPart。 Director和Builder形成一种聚合关系。这意味着builder是一个组成部分,与Director结合,以使整个模式运转,但同时,Director并不负责Builder的生存期。
生成器模式:将一个复杂对象的构建与它的表现分离,使得同样的构建过程可以创建不同的表现。
何时使用生成器模式
- 需要创建涉及各种部件的复杂对象。创建对象的算法应该独立于部件的装配方式。常见例子是构建组合对象。
- 构建过程需要以不同的方式构建对象。
生成器与抽象工厂的对比
两者大不相同,一方面生成器关注的是分步创建复杂对象,很多时候同一类型的对象可以以不同的方式创建。另一方面,抽象工厂的重点在于创建简单或复杂产品的套件。生成器在多步创建过程的最后一步返回产品,而抽象工厂则立即返回产品。
两者的区别主要是:
生成器: 构建复杂对象;以多个步骤构建对象;以多种方式构建对象;在构建过程的最后一步返回产品;专注一个特定产品。
抽象工厂:构建简单或复杂对象;以单一步骤构建对象;以单一方式构建对象;立刻返回产品;强调一套产品。
总结: 生成器模式能帮助构建涉及部件与表现的各种组合的对象。没有这一模式,知道构建对象所需细节的Director可能最终会变成一个庞大的神类。
下一章将讨论一种只生成并返回类的单一实例的模式。
第7章 单 例
这一模式的意图是使得类的一个对象成为系统中的唯一实例。要实现这一点,可以从客户端对其进行实例化开始。
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
何时使用单例模式
- 类只能有一个实例,而且必须从一个为人熟知的访问点对其进行访问,比如工厂方法;
- 这个唯一的实例只能通过子类化进行扩展,而且扩展的对象不会破坏客户端代码。
单例模式提供了一个为人熟知的访问点,供客户类为共享资源生成唯一实例,并通过它对共享资源进行访问。虽然静态的全局对象引用或类方法也可以提供全局访问点,但是全局对象无法防止类被实例化一次以上,而且类方法也缺少消除耦合的灵活性。
单例类提供创建与访问类的唯一对象的访问点,并保证它唯一、一致而且为人熟知。这一模式提供了灵活性,使其任何子类可以重载实例方法并且完全控制自身的对象创建,而不必修改客户端的代码。更好的是,父类中的实例实现可以处理动态对象创建。类的实际类型可以在运行时决定,以保证创建正确的对象。
在OC中实现单例模式
要把一个单例类设计得恰当,有些事情必须要考虑清楚。需要问的第一个问题是,如何保证类只能创建一个实例。
如果需要实现单例模式的“严格”版本,要想在实际中使用,需要面对以下两个主要的障碍。
- 发起调用的对象(calling object)不能以其他分配方式实例化单例对象。否则,就有可能创建单例类的多个实例。
- 对单例对象实例化的限制应该与引用计数内存模型共存。
线程安全
如果单例对象由多个线程访问,那么使它线程安全(thread-safe)至关重要。要让它线程安全,需要在sharedSingleton_静态实例的nil检查周围加入一些@synchronized()程序块或者NSLock实例。如果有其他属性需要保护,也可以把它们声明为atomic型。