设计模式的描述设计模式设计模式之间的对比 设计模式 设计模式之间的对比

文章目录

吐血…终于码完了大论文,学习一下设计模式,尝试搞明白怎样才能做到“面向对象”

参考:《设计模式:可复用面向对象软件的基础》

设计模式与iOS

Objective-C编程之道:iOS设计模式解析

按照目的准则将设计模式分为创建型、结构型、行为型。创建型模式与对象的创建有关,结构型模式处理类或者对象的组合,行为型模式对类或者对象怎样交互和怎样分配指责进行描述。

面向对象设计最困难的部分是将系统分解成对象集合,因为要考虑的因素很多:封装、粒度、依赖关系、灵活性、性能、演化、复用等等,它们都影响着系统的分解,并且这些因素通常还是相互冲突的。

发送给对象的请求和它的相应操作在运行时刻的连接称为动态绑定。动态绑定允许在运行时刻批次替换有相同接口的对象,这种可替换性称为多态。多态简化了客户的定义,使得对象间彼此独立,并可以在运行时刻动态改变它们的相互关系。

抽象类:抽象类的主要目的是为它的子类定义公共接口。一个抽象类能把它的部分或者全部操作的实现延迟到子类中,因此,一个抽象类不能被实例化。在抽象类中定义却没有实现的操作被称为抽象操作。

混入类:混入类是给其他类提供可选择的接口或者功能的类。它与抽象类一样不能实例化。混入类要求多继承。

面向对象系统中功能复用的两种最常用技术是类继承和对象组合。类继承这种通过生成子类的复用通常被称为白箱复用,在继承方式中,父类的内部细节对子类可见;对象组合是通过组装或者组合对象来获得,对象组合要求被组合的对象具有良好定义的接口,这种复用风格被称为黑箱复用,因为对象的内部细节是不可见的。

类继承是在编译时刻静态定义的,并且可以直接使用,因为程序设计语言直接支持类继承。但是因为类继承在编译时刻就已经定义好了,所以无法在运行时刻改变从父类继承的实现。而且,父类通常至少定义了部分子类的具体表示,因为继承对子类揭示了父类的实现细节,破坏了封装性。子类中的实现和父类有紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。这种依赖关系限制了灵活性并最终限制了复用性。

对象组合是通过获得对其他对象的引用而在运行时刻动态定义的。组合要求对象遵守彼此的接口约定,进而要求更仔细的定义接口,而这些接口并不妨碍将一个对象和其他对象一起使用。因为对象只能通过接口访问,所以我们并不破坏封装性,只要类型一致,运行时刻还可以用一个对象来替代另一个对象,更进一步,因为对象的实现是基于接口写的,所以实现上存在较少的依赖关系。

设计模式

创建型模式

abstract factory 抽象工厂

提供一个创建一些列相关或相互依赖对象的接口,而无需指定它们具体的类。

在面向对象的概念中,所有的对象都是通过类描绘的,但是并不是所有的类都是用来描绘对象的。如果一个类中没有包含足够的信息来描绘一个对象,这样的类就是抽象类。抽象类出了不能实例化对象之外,类的其他功能仍然存在,成员变量、成员方法以及构造方法的访问方式和普通类一致。

AbstractFactory方法的特点:

  1. 它分离了具体的类。抽象类封装了对象的创建过程,能够将类和对象之间分离,客户可以通过抽象类提供的抽象接口操纵实例。
  2. 它使实例易于修改。一个具体的工厂类在一个应用中只出现一次,也就是在它初始化的时候,可以通过修改这个抽象类来实现对继承于这个抽象类的所有实例的修改。
  3. 它有利于实例的一致性。设计成一个系列,保证这个系列的实例的一致。
  4. 难以支持新种类的产品。难以扩展抽象工厂以生产新种类的产品。如果扩展抽象接口,将会涉及到所有的实例的改变。

研究了一下发现:在iOS中的“类簇”其实就是抽象工厂。例如NSNumber调用初始化方法后返回的对象是NSCFNumberNSCFBoolean工厂,这两个工厂继承自NSNumber,拥有同样的一些方法,例如intValueboolValue,在NSNumber角度来看,这就是抽象工厂。

builder 生成器

生成器模式是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

以下情况使用builder模式:

  1. 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时;
  2. 当构造过程必须允许被构造的对象有不同的表示时。

生成器模式和抽象工厂有什么区别?

生成器 抽象工厂
构建复杂对象 构建简单或者复杂对象
以多个步骤构建对象 以单一步骤构建对象
以多种方式构建对象 以单一方式构建对象
在构建过程的最后一步返回产品 立刻返回产品
专注一个特定产品 强调一套产品

builder模式的效果:

  1. 可以改变一个产品的内部表示。builder对象提供给导向器一个构造产品的抽象接口,该接口使得生成器可以隐藏这个产品的表示和内部结构,同时也隐藏了该产品是如何装配的。因为产品是通过抽象接口构造的,在改变产品的内部表示时所要做的只是定义一个新的生成器。
  2. 将构造代码和表示代码分开。builder模式通过封装一个复杂对象的创建和表示方式提高了对象的模块性,客户不需要知道定义产品内部结构的类的所有信息,这些类是不出现在builder接口中的。每个concreteBuilder包含了创建和装配一个特定产品的所有代码。这些代码只需要写一次,然后不同的director可以复用它以在相同部件集合的基础上构建不同的product。
  3. 可以实现对构造过程的更精细的控制。builder模式与一下子就生成产品的创建型模式不同,他是在导向者的控制下一步一步构造产品。仅当该产品完成时导向者才从生产器中取回它,因此builder接口相比其他创建型模式能够更好的反映产品的构造过程,实现更精细的构建过程,更精细的控制产品的内部结构。

factory mothod 工厂方法

定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂模式使一个类的实例化延迟到其子类。也叫虚构造器(Virtual Constructor)。

这个设计模式在iOS开发中,例子也很常见,比如NSNumber的API中,有类初始化方法,将会产生两种类型,NSCFNumberNSCFBooleanboolValueintValue方法将会生成具体的对象,NSCFNumberNSCFBoolean就是所谓的工厂模式的体现。

适用性:

  • 当一个类不知道它所必须创建的对象的类的时候。
  • 当一个类希望由它的子类来指定它所创建的对象的时候。
  • 当类将创建对象的职责委托给多个帮助子类中的某一个,并且希望将哪一个帮助子类是代理者这一信息局部化的时候。

工厂方法还可以为子类提供hook;连接平行的类层次。

prototype 原型

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

当一个系统应该独立于它的产品创建、构成和表示时,需要使用Prototype模式,以及当要实例化的类是在运行时刻指定时,或者为了避免创建一个与产品类层次平行的工厂类层次时,或者当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次使用合适的状态手工实例化该类要方便一些。达到的效果是对客户隐藏了具体的产品类,因此减少了客户知道的名字的数目,此外,还可以使客户无需改变即可使用与特定应用相关的类,与抽象工厂模式和生成器模式达到的效果相同。

JavaScript中使用原型模式,在iOS开发中好像没见到相关的应用,但是copy操作和原型模式的克隆方法可以做一个分辨。

原型模式的另外一些优点:

  1. 运行时刻增加或者删除产品。原型允许只通过客户注册原型实例就将一个新的具体的产品类并入系统。它比其他的创建型模式更为灵活,因为客户可以在运行时刻建立和删除原型。
  2. 改变值以指定新的对象。高度动态的系统允许通过对象符合定义新的行为,例如通过为一个对象变量指定值,而且并不定义新的类。通过实例化已有类并且将这些实例注册为客户对象的原型,就可以有效定义新类别的对象。客户可以将职责代理给原型,从而表现出新行为。这种设计使得用户无需编程即可定义新的类,实际上克隆一个原型类似于实例化一个类。原型模式可以极大的减少系统所需要的类的数目。
  3. 改变结构以指定新对象。许多应用由部件和子部件来创建对象,为了方便起见,这样的应用通常允许实例化复杂的、用户定义的结构。原型模式也支持这一点,只要将对象clone实现为深拷贝即可。
  4. 减少子类的构造。工厂方法通常会产生一个与产品类层次平行的creator类层次。原型模式使得你克隆一个原型而不是请求一个工厂方法去产生一个新的对象,因此根本不需要creator类层次。这一优点主要适用于像C++这样的不将类作为一级类对象的语言。但是像oc,smalltalk这样的语言,总是可以用一个类对象作为生成者,类对象已经起到了原型一样的作用。
  5. 用类动态配置应用。一些运行时刻环境允许动态的将类装载在应用中。在像C++这样的语言中,原型模式是利用这种功能的关键。一个希望创建动态载入类的实例的应用不能静态引用类的构造器,而应该由运行环境在载入时自动创建每个类的实例,并用原型管理器来注册这个实例,这样应用就可以想原型管理器请求新装载的类的实例,这些类原本并没有和程序相连接。
  6. 原型模式的主要缺陷是每一个原型的子类都必须实现clone操作,这样可能很困难。例如当所考虑的类已经存在时就难以新增clone操作。当内部包括一些不支持拷贝或者有循环引用的对象时,实现克隆可能变得困难。

这里我的上一篇关于JavaScript中的原型的文章可以作为一个例子帮助理解。JavaScript中的继承和原型链

singleton 单例

单例模式可以保证一个类只有一个实例,并提供一个访问它的全局访问点。

想要达到这一目的,一个办法是让类自身负责保存它的唯一实例,这个类可以保证没有其他实例可以被创建(通过截取创建新对象的请求),并且可以提供一个访问该实例的方法。

在iOS中单例使用很多,例如UIApplication以及NSFileManager

结构型模式

adapter 适配器

将一个类的接口转换成客户希望的另一个接口,adapter模式使得原本由于接口不兼容而不能一起工作的类可以一起工作。

在iOS开发中也有用到适配器模式,当引入某个SDK中一些方法和app不兼容时,利用协议为适配器类约定方法,将SDK中方法封装到协议方法里,并添加上自己app需要进行的处理。

以下情况使用adapter模式:

  1. 想使用一个已经存在的类,而它的接口不符合需求
  2. 想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作
  3. 仅适用于对象adapter:想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口,对象适配器可以适配它的父类接口。

类适配器和对象适配器有不同的权衡。

类适配器:
类适配器
对象适配器:
对象适配器

  • 类适配器使用多重继承对一个接口与另一个接口进行匹配
    • 用一个具体的adapter类对adaptee和target进行匹配。结果是当我们想要匹配一个类以及所有的子类时,类adapter将不能胜任工作。
    • 使用adapter可以重定义adaptee的部分行为,因为adapter是adaptee的一个子类。
    • 仅仅引入了一个对象,并不需要额外的指针以间接得到adaptee。
  • 对象适配器依赖于对象组合
    • 允许一个adapter与多个adaptee,即adaptee本身以及它的所有子类同时工作。adapter也可以一次给所有的adaptee添加功能。
    • 使得重定义adaptee的行为比较困难,这就需要生成adaptee的子类并且使得adapter引用这个子类而不是引用adaptee本身。

bridge 桥接

桥接模式是将抽象部分和它的实现部分分离,使它们都可以独立的变化。

当一个抽象可能有多个实现时,常用继承来协调它们。抽象类定义对该抽象类的接口,而具体的子类则用不同方式加以实现,但是此方法有时不够灵活,继承机制将抽象部分与它的实现部分固定在一起,使得难以对抽象部分和实现部分独立的进行修改、扩充和重用。

以下情况适用桥接模式:

  • 不希望在抽象和它的实现部分之间有一个固定的绑定关系,可能是因为在程序运行时刻实现部分应该可以被选择或者切换。
  • 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充,这时桥接模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
  • 对一个抽象的实现部分的修改应该对客户不产生影响,即客户的代码不必重新编译。
  • 想对客户完全隐藏抽象的实现部分。在C++中,类的表示在类接口中是可见的。
  • 有许多类要生成,这样的类层次结构说明你必须将一个对象分解为两个部分,称为“嵌套的普化”。
  • 想再多个对象间共享实现,可能使用引用计数,但是同时要求客户并不知道这一点。

composite 组合

将对象组合成树形结构以表示“整体-部分”的层次结构,使用户对单个对象和组合对象的使用具有一致性。

iOS中的UIKit框架中UIView的设计能够体现组合设计模式。UIView是可以添加UIView类和其子类对象的,对象的渲染以及其他操作会遍历到子类中依次进行。

在实现组合模式的时候需要考虑的几个问题:

  1. 显式的父部件引用。保持从子部件到父部件的引用能简化组合结构的遍历和管理。父部件的引用可以简化结构的上移和组件的删除,同时父部件引用也支持Chain of Responsibility。通常在组合类中定义父部件引用,LeafComposite类可以继承这个引用以及管理这个引用的那些操作。对于父部件的引用,必须维护一个不变式,即一个组合的所有子节点以这个组合为父节点,而反之该组合以这些节点为子节点。保证这一点最容易的方法是,仅当在一个组合中增加或者删除一个组件时,才改变这个组件的父部件。如果能在组合类的增加和删除操作中实现这种方法,那么所有的子类都可以继承这一方法,并且将自动维护这一不变式。
  2. 共享组件。
  3. 最大化组合接口。组合模式的目的之一是使得用户不知道他们正在使用的具体的LeafComposite类,为了达到这一目的,组合类应该为LeafComposite类尽可能多定义一些公共接口。
  4. 声明管理子部件的操作。需要在安全性和透明性之间作出权衡选择:
    • 在类层次结构的根部定义子节点管理接口的方法具有良好的透明性,因为可以一致的使用所有的组件,但是这一方法是以安全性为代价的,因为客户有可能会做一些无意义的事情,例如在Leaf中增加和删除对象等。
    • Composite类中定义管理子部件的方法具有良好的安全性,因为在C++这样的静态类型语言中,在编译时任何从Leaf中增加或者删除对象的尝试都将被发现,但是这又损失了透明性,因为这样LeafComposite具有不同的接口。
  5. 子部件排序。
  6. 使用高速缓冲存储改善性能。
  7. 垃圾回收机制。
  8. 储存的数据结构。可以用到的包括列表、树、数组、hash表。

dectorator 装饰

将组件嵌入另一个对象中,这个嵌入的对象称为装饰。这个装饰和它所装饰的组件接口一致,因此它对使用该组件的客户透明。它将客户请求转发给该组件,并且可能在转发前后执行一些额外的动作。透明性使得你可以递归的嵌套多个装饰,从而可以添加任意多的功能。

以下情况使用装饰模式:

  1. 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责;
  2. 处理可以撤销的职责;
  3. 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种可能的情况是因为类定义被隐藏,或者类定义不能用于生成子类。

优点和缺点:

  1. 比静态继承更灵活。和对象的静态继承相比,装饰者模式提供了更加灵活的向对象添加职责的方式,可以用添加和分离的方法,用装饰在运行时刻增加和删除职责。相比之下,继承机制要求为每个添加的职责创建一个新的子类,这会产生很多新的类,并且会增加系统的复杂度。此外,为一个特定的组件类提供多个不同的装饰者类,使得你可以对一些职责进行混合和匹配。
  2. 避免在层次结构高层的类有太多的特征。
  3. decorator是一个透明的包装,如果从对象标示的角度出发,一个被装饰了的组件和这个组件是有差别的,因此,使用装饰时不应该依赖对象标示。
  4. 有许多小对象。很容易进行定制,但是很难学习这些系统,debug困难。

iOS中的分类(Category)是一种变相装饰者模式,他的原理是在编译的时候动态的为原来的类添加方法,而不是拥有一个原始类的实例,严格意义上不是装饰者,但是和装饰者思想很像。

facade 外观

为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

将一个系统划分成为若干个子系统有利于降低系统的复杂性,一个常见的设计目标就是使子系统间的通信和相互依赖关系达到最小,达到该目标的途径之一是引入一个外观对象,为子系统中较一般的设施提供了一个单一而简单的界面。

适用性:

  • 当你要为一个复杂的子系统提供一个简单接口时,子系统往往因为不断演化而变得越来越复杂,大多数模式使用时都会产生更多更小的类,这使得子系统更具有可重用性,也更容易对子系统进行定制,但是这也给哪些不需要定制子系统的用户带来一些使用上的困难。外观模式可以提供一个简单的缺省试图,这一世图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过外观层。
  • 客户程序与抽象类的实现部分之间存在着很大的依赖性,引入外观模式将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。
  • 当你需要构建一个层次结构的子系统时,使用外观模式定义子系统中每个层的入口点,如果子系统之间是相互依赖的,你可以让它们仅仅通过外观进行通讯,从而简化了它们之间的依赖关系。

flyweight 享元

运用共享技术有效的支持大量细粒度对象。

flyWeight是一个共享对象,它可以同时在多个场景中使用,并且每个场景中的flyWeight都可以作为一个独立的对象,这一点与非共享对象的实例没有区别。flyWeight不能对它所运行的场景作出任何假设,这里的关键概念是内部状态和外部状态之间的区别。内部转改存储于flyWeight中,它包含了独立于flyWeight场景的信息,这些信息使flyWeight可以被共享。而外部状态取决于flyWeight场景,并根据场景而变化,因此不可共享。用户对象负责在必要的时候将外部状态传递给flyWeight。flyWeight模式对那些通常因为数量太大而难以用对象来表示的概念或者实体进行建模。

享元模式的有效性很大程度上取决于如何使用它以及在何处使用。一下情况都成立的时候使用享元模式:

  1. 一个应用程序使用了大量的对象;
  2. 完全由于使用大量的对象,造成了很大的存储开销;
  3. 对象的大多数状态都可变为外部状态;
  4. 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象;
  5. 应用程序不依赖于对象标示。由于享元模式对象可以被共享,对于概念上明显有别的对象,标识测试将返回真值。

proxy 代理

为其他对象提供一种代理以控制对这个对象的访问。

代理模式在iOS中应用十分常见,协议、代理、委托对象共同构成了代理模式:

  • 协议:约束代理对象需要进行的操作
  • 代理:执行被指派的操作
  • 委托:指派任务的角色

在需要用比较通用和复杂的对象指针代替简单的指针的时候,使用proxy模式,一些常见的使用这种代理模式的场景:

  1. 远程代理:为一个对象在不同的地址空间提供局部代理。
  2. 虚代理:根据需要创建开销很大的对象。
  3. 保护代理:控制对原始对象的访问,用于对象应该有不同的访问权限的时候。
  4. 智能指引:取代了简单的指针,在访问对象时执行一些附加操作,它的典型用途包括:
    • 对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它;
    • 当第一次引用一个持久对象时,将它装入内存;
    • 在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。

代理模式在访问对象时引入了一定程度的间接性,远程代理可以隐藏一个对象存在于不同地址空间的事实;虚代理剋呀进行最优化,例如可以更具要求创建对象;保护代理和职能指引都允许在访问一个对象时有一些附加的内务处理。

代理模式还可以对用户隐藏写时复制(copy-on-write)的优化方式,该油画与根据需要创建对象有关。拷贝一个庞大而复杂的对象开销很大,对于没有修改的拷贝,开销没有必要,用代理延迟这一拷贝过程,保证只有当这个对象被修改的时候才能进行拷贝。

行为模式

chain of responsibility 职责链

使多个对象都有机会处理请求,从而避请求的发送者和接受者之间的耦合关系。将这些对象连城一条链,并沿着这条链传递该请求,知道有一个对象处理它为止。

在以下情况下使用职责链:

  1. 有多个对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。
  2. 想在不明确指定接受者的情况下,想多个对象中的一个提交一个请求。
  3. 可处理一个请求的对象集合应被动态指定。

职责链的优缺点:

  1. 降低耦合度。该模式使得一个对象无需知道是其他哪一个对象处理其请求,对象仅需知道该请求会被正确的处理,接受者和发送者都没有对方的明确信息,并且链中对象不需要知道链的结构。结果是,职责链可以简化对象的相互连接,仅需保持一个指向其后继者的引用,而不需要保持它所有的候选者的引用。
  2. 增强了给对象指派职责的灵活性。挡在对象中分派职责时,职责链有更多的灵活性,可以通过在运行时刻对该链进行动态的增加或者修改来增加或者改变处理一个请求的职责,可以将这种机制与静态的特例化处理对象的继承机制结合起来使用。
  3. 不保证被接受。既然一个请求没有明确的接受者,那么就不能保证它一定会被处理,一个情切也可能因为该链没有正确配置而没有被处理。

command 命令

将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。

iOS中的NSInvocation以及NSUndoManager借助了这种设计模式。前者是一种消息调用方法,后者可以提供撤销机制。

以下情况可使用命令模式:

  1. 抽象出等待执行的动作以参数化某对象。可以使用过程语言中的回调函数表达这种参数化的机制。命令模式是回调机制的一个面向对象的替代品。
  2. 在不同的时刻指定、排列或者执行请求。一个命令对象可以有一个与初始请求无关的生存期,如果一个请求的接收者可用一种与地址空间无关的方式表达,那么就可以将负责该请求的命令对象传送给另一个不同的进程并在那里实现这个请求。
  3. 支持取消操作。
  4. 支持修改日志。
  5. 用构建在原语操作上的高层操作构造一个系统。这样一种结构在支持事务的信息系统中很常见。一个事务封装了对数据的一组变动。命令模式提供了对事务进行建模的方法。命令有一个公共的接口,使你可以使用同一种方式调用所有的事物,同时使用该模式也易于添加新事务以扩展系统。

interpreter 解释器

iterator 迭代器

提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。这不是应该叫枚举器之类的吗…

在iOS开发中NSEnumerator是迭代器类,可以用于对常用的集合对象进行访问和操作。

迭代器和列表是耦合在一起的,而且客户对象必须知道遍历的是一个列表而不是其他聚合对象,最好能有一种办法使得不需要改变客户代码即可改变该聚合类,可以通过将迭代器的概念推广到多态迭代来达到这个目标。

迭代器的适用性:

  • 访问一个聚合对象的内容而无需暴露它的内部表示;
  • 支持对聚合对象的多种遍历;
  • 为遍历不同的聚合结构提供一个统一的接口,即支持多态迭代器。

mediator 中介器

用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式的相互引用,从而使其耦合松散,而且可以独立的改变它们之间的交互。

使用中介者的场景:

  • 对象间的交互虽然定义明确但是非常复杂,导致一组对象彼此相互依赖而且难以理解;
  • 因为对象引用了许多其他对象并与其通讯,导致对象难以复用;
  • 想要定制一个分布在多个类中的逻辑或行为,又不像生成太多子类。

memento 备忘录

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

别名:Token

一个备忘录是一个对象,它储存另一个对象在某个瞬间的内部状态,而后者称为备忘录的原发器。当需要设置原发器的检查点时,取消操作机制会向原发器请求一个备忘录。原发器用描述当前状态的信息初始化该备忘录。只有原发器可以向备忘录中存取信息,备忘录对其他的对象“不可见”。

以下情况使用备忘录模式:

  1. 必须保存一个对象在某个时刻的(部分)状态,这样以后需要时餐能恢复到先前的状态。
  2. 如果一个用接口来让其他对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。

在iOS中使用备忘录模式的地方:

  • 归档
    • 备忘录对象即为归档对象
    • 被归档的对象为操作对象(原发器)
    • 文件系统则为备忘录管理者
  • 属性列表序列化

再如游戏进度的存储和读取。

observer 观察者

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

将一个系统分割成一系列相互协作的类有一个常见的副作用:需要维护相关对象间的一致性。我们不希望为了维持一执行而使各类紧密耦合,因为这样降低了它们的可重用性。

以下适用于观察者模式:

  1. 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这二者封装在独立的对象中以使他们可以各自独立的改变和复用。
  2. 当对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变。
  3. 当一个对象必须通知其他对象,而它又不能假定其他对象是谁。换言之,不希望这些对象时紧密耦合的。

观察者模式的一些优缺点:

  1. 目标和观察者之间的抽象耦合。一个目标所知道的仅仅是它由一系列的观察者,每个都符合抽象的观察者累的简单接口,目标不知道任何一个观察者属于哪一个具体的类,这样目标和观察者之间的耦合是抽象的和最小的。因为目标和观察者不是紧密耦合的,它们可以属于一个系统中的不同抽象层次,一个处于较低层次的目标对象可与一个处于较高层次的观察者通信并通知他,这样就保持了系统层次的完整。如果目标和观察者混在一起,那么得到的对象要么横贯两个层次(违反了层次性),要么必须放在这两层的某一层中(这样可能会损害抽象层次)。
  2. 支持广播通信。不像通常的请求,目标发送的通知不需要指定他的接受者。通知被自动广播给所有已向该目标对象登记的有关对象。目标对象并不关心到底有多少对象对自己感兴趣,它唯一的责任就是通知他的观察者,这给了在任何时刻增加或者删除观察者的自由,处理或者是忽略一个通知取决于观察者。
  3. 意外的更新。因为一个观察者并不知道其他的观察者的存在,它可能会改变目标的最终代价一无所知。在目标上一个看似无害的操作可能会引起一系列对观察者以及依赖于这些观察者的那些对象的更新。此外,如果依赖准则的定义或者维护不当,常常会引起错误的更新,这些错误常常难以捕捉。

在iOS中有两种观察者模式的实现:

  1. 通知机制
  2. KVO

state 状态

允许一个对象在其内部状态改变时改变他的行为,效果是对象看起来似乎修改了类。

以下情况适用:

  • 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
  • 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或者多个枚举常量表示。通常,有多个操作包含这一相同的条件结构。状态模式将每一个条件分支放入一个独立的类中,这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。

strategy 策略

定义一系列的算法,把它们一个个封装起来,并且使它们
可以相互替换,使得算法可以独立于使用它的客户而变化,避免将算法的具体细节暴露给外部。

在以下情况使用策略模式:

  1. 许多相关的类仅仅是行为有区别。策略提供了一种用多个行为中的一个行为来配置一个类的方法。
  2. 需要使用一个算法的不同变体。
  3. 短发使用用户不应该知道的数据。
  4. 一个类定义个了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入个字的策略类中来代替这些语句。

在iOS开发中,MVC模式中涵盖了策略模式,体现为:控制器作为试图的策略类,视图在没有控制器的情况下,显示应该是一样的,但是不同的控制器将会给视图赋予不同的数据以及输出模式。

template method 模版方法

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod使得子类可以不改变一个算法的结构及可以重新定义该算法的某些特定步骤。

在iOS中最典型的例子是UIView中的drawRect方法,该方法是用于描述UIView长什么样子,这个方法留给子类去子类去实现,从而实现不同绘制。

适用性:

  1. 一次性实现一个算法的不变的部分,并将可变的行为留给子类实现;
  2. 各子类中公共的行为应该被提取出来并集中到一个公共的父类中,以避免代码重复。首先识别代码中的不同之处,并且将不同之处分离为不同的操作,最后用一个调用这些新的操作的方法来替换这些不同的代码。
  3. 控制子类的扩展。模版方法只在特点电调用“hook”操作,这样就只允许在这些点进行扩展。

模版方法调用下列类型的操作:

  • 具体的操作
  • 具体的AbstractClass的操作
  • 原语操作,即抽象操作
  • Factory Method
  • hook operation。它提供了缺省的行为,子类可以在必要时进行扩展,一个钩子操作在缺省操作中通常是一个空操作。

很重要的一点是模版方法应该指明哪些操作是钩子操作,可以被重定义,以及哪些是抽象操作,必须被重定义。要有效的重用一个抽象类,子类编写者必须明确连接哪些操作是设计为有待重定义的。在父类的模版中调用钩子操作。子类可以重定义这个钩子操作。

visitor 访问者

表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

使用访问者模式的场景:

  • 一个对象结构包含很多类对象,它们有不同的接口,而你相对这些对象实施一些依赖于其具体类的操作。
  • 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。访问者是你可以将相关的额操作集中起来定义在一个类中,当该对象结构被很多应用共享的时候,用访问者模式让每个应用仅仅包含需要用到的操作。
  • 定义对象结构的类很少改变,氮经常需要用到在此结构下定义的新的操作。改变对象结构类需要重新定义对所有访问者的接口,这可能需要付出很大的代价,如果对象结构类经常改变,那么可能还是在这些类中定义这些操作比较好。

设计模式之间的对比

创建型模式的讨论

在一个系统创建的那些对象的类对系统进行参数化通常由两种方式,一种是生成创建对象的类的子类,对应于Factory Method模式。这种方法的主要缺点是,仅为了改变产品的类,就可能需要创建一个新的子类,这样的改变是联级的,例如产品的创建者本身是一个工厂方法创建的,那么必须重新定义的它创建者。

另一种对系统进行参数化的方法更多依赖于对象复合:定义一个对戏那个负责明确产品对象的类,并将它最为系统的参数。这是Abstract Factory、Builder、Prototype模式的关键特征。所有者三个模式都涉及到创建一个新的负责创建产品对象的工厂对象。Abstract Factory由这个工厂对象产生多个类的对象。Builder由这个工厂对象使用一个相对复杂的协议,逐步创建一个复杂产品。Prototype由该工厂对象通过拷贝原型对象来创建产品对象,因为原型负责返回产品对象,所以工厂对象和原型是同一个对象。

Factory Method使一个设计可以定制且只略微有一点复杂。其他设计模式需要新的类,而Factory Method只需要一个新的操作。人们通常将Factory Method作为一种标准的创建对象的方法,但是当被实例化的类根本不发生变化或者放实例化出现在子类可以很容易定义的操作中时,比如在初始化操作中,就没有这个必要了。

使用Abstract Factory、Builder、Prototype的设计甚至要比使用Factory Method的那些设计更加灵活,但是也更加复杂。通常,设计以使用Factory Method开始,并且当设计者发现需要更大的灵活性的时候,设计便会向其他的创建型模式演化。

结构型模式的讨论

结构型模式之间有很大相似性,尤其是它们的参与者和协作之间的相似性,可能是因为结构型模式依赖于同一种很小的语言机制集合构造代码和对象:单继承和多重继承机制用于基于类的模式,而对象组合机制用于对象式模式。

adapter模式bridge模式具有一些共同特征,它们都会给另一对象提供一定程度上的间接性,而有利于系统的灵活性。它们都涉及到从自身以外的一个接口向这个对象转发请求。

adapter模式主要是为了解决两个已有接口之间不匹配的问题,它不考虑这些接口是怎么实现的,也不考虑它们各自可能会如何演化。这种方式不需要对两个独立设计的类中的任意一个进行重新设计,就能够实现协同工作。bridge模式则对抽象接口与它的(可能是多个)实现部分进行桥接。虽然这一设计模式允许修改实现它的类,它仍然为用户提供了一个稳定的接口,bridge模式也会在系统演化时适应新的实现。

decorator旨在是你能够不想要生成子类就可以给对象添加职责,避免了静态实现所有功能组合导致的子类急剧增加。composite旨在构造类,使多个相关的对象能够以统一的方式处理,而多重对象可以被当做一个对象来处理,重点不在修饰,在于表示。proxy构成一个对象并为用户提供一致的接口,但与decorator不同的是,proxy模式不能动态的添加或者分离性质,也不是为了递归组合而设计的,它的目的是,当直接访问一个实体不方便或者不符合需求时,为这个实体提供一个替代者。

行为模式的讨论

  • strategy对象封装一个算法
  • state对象封装一个与状态相关的行为
  • mediator对象封装对象间的协议
  • iterator对象封装访问和遍历一个聚集对象的各个构件的方法

visitor对象是一个多态的accept操作的参数,这个操作作用于该visitor对象访问的对象。

command模式中多态很重要,因为执行command对象是一个多态的操作,相反,memento接口非常小,以至于备忘录职能作为一个值传递,因此它很可能根本不给它的客户提供任何多态操作。

mediator和observer是相互竞争的模式,它们之间的差别是:observer通过引入observer和subject对象来分布通信,而mediator对象则封装了其他对象间的通信。

当合作的对象直接相互引用时,它们变得相互依赖,这可能会对一个系统的分层和重用性产生负面影响。命令、观察者、中介者和职责链等模式都涉及如何对发送者和接受者解耦,但是又各自有不同的考虑。command模式使用一个command对象来定义一个发送者和一个接收者之间的绑定关系,从而支持解耦。观察者模式通过定义一个接口来通知目标中发生的改变,从而将发送者(目标)与接收者(观察者)解耦。中介者模式让对象通过一个mediator对象间接的互相引用,从而对它们解耦。职责链模式通过沿一个潜在的接收者链传递请求而将发送者与接收者之间解耦。

Command模式:
Command模式

Observer模式:
Observer模式

Mediator模式:
Mediator模式

Chain of respinsibility模式:
Chain of respinsibility模式