第七部分 算 法 封 装
第18章 模 板 方 法
何为模板方法模式
模板方法模式是面向对象软件设计中一种非常简单的设计模式。其基本思想是在抽象类的一个方法中定义“标准”算法。在这个方法中调用的基本操作应由子类重载予以实现。这个方法被称为“模板”,因为方法定义的算法缺少一些特有的操作。抽象类定义模板,子类重载基本操作以提供独特操作供模板方法调用。
模板方法模式:定义一个操作中算法的骨架,而将一些步骤延迟到子类中。模板方法使子类可以重定义算法的某些特定步骤而不改变该算法的结构。
何时使用模板方法
- 需要一次性实现算法的不变部分,并将可变的行为留给子类来实现。
- 子类的共同行为应该被提取出来放到公共类中,以避免代码重复。现有代码的差别应该被分离为新的操作。然后用一个调用这些新操作的模板方法来替换这些不同的代码。
- 需要控制子类的扩展。可以定义一个在特定点调用“钩子”操作的模板方法。子类可以通过对钩子操作的实现在这些点扩展功能。
钩子操作给出了默认行为,子类可对其扩展。默认行为通常什么都不做。子类可以重载这个方法,为模板算法提供附加的操作。模板方法模式中的控制结构流程是倒转的,因为父类的模板方法调用其子类的操作,而不是子类调用父类的操作。
模板方法会调用5种类型的操作:
- 对具体类或客户端类的具体操作;
- 对抽象类的具体操作;
- 抽象操作;
- 工厂方法;
- 钩子操作。
模板方法与委托方法的比较
模板方法和委托模式(也叫适配器模式,见第8章)常见于cocoa touch 框架。它们对框架类设计来说是非常自然的选择。为什么呢?用户应用程序可以复用(或扩展)框架类,而且框架类在设计时不会知道什么样的类会使用它们。可是对于特定的软件设计问题应该使用哪一个模式呢?以下是简要的指导方针。
模板方法: 父类定义一个一般算法,但缺少某些特定/可选的信息或算法,它通过这些缺少的信息或算法起到一个算法“食谱”的作用;缺少的信息由子类通过继承来提供。
委托模式(适配器):委托与预先定义好的委托接口一起定义一个特定算法;特定算法由任何对象通过对象组合来提供。
在cocoa touch框架中使用模板方法
在框架设计中,模板方法模式相当常见。模板方法是代码复用的基础技术。通过它,框架的设计师可以把算法中应用程序相关的元素留给应用程序去实现。模板方法是抽出共同行为放入框架类中的手段。这一方式有助于提高可扩展性与可复用性,而维持各种累到这些框架类,虽然不如delegation那么常见。
总结:
模板方法模式是代码复用的一项基本技术。模板方法在框架类设计中非常重要,因为它是抽出共同行为放入框架类中的手段。
下一章将讨论另一种把对象中的算法封装成各种策略的方式。
第 19 章 策 略
面向对象软件设计中,我们可以把相关算法分离为不同的类,成为策略。与这种做法有关的一种设计模式称为策略模式。
何为策略模式
策略模式中的一个关键角色是策略类,它为所有支持的或相关的算法声明了一个共同接口。另外,还有使用策略接口来实现相关算法的具体策略类。场景(context)类的对象配置有一个具体策略对象的实例,场景对象使用策略接口调用由具体策略类定义的算法。
策略模式:定义一系列算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
模型-视图-控制器中的策略模式
模型-视图-控制器模式中,控制器决定视图对模型数据进行显示的时机和内容。视图本身知道如何绘图,但需要控制器告诉它要显示的内容。同一个视图如果与不同的控制器合作,数据的输出格式可能一样,但数据的类型和格式可能随不同控制器的不同输出而不同。因此这种情况下的控制器是视图的策略。
何时使用策略模式
- 一个类在其操作中使用多个条件语句来定义许多行为。我们可以把相关的条件分支移到它们自己的策略类中。
- 需要算法的各种变体。
- 需要避免把复杂的、与算法相关的数据结构暴露给客户端。
总结:
本章讨论了策略模式的概念以及如何应用这个模式让客户(场景)类使用相关算法的各种变体,为定制的UITextField实现输入验证器的例子,示范了各种验证器如何更换定制的UITextField的“内容”。策略模式与装饰模式有些相似。装饰器从外部扩展对象的行为,而各种策略则被封装在对象之中。所以说装饰器改变对象的“外表”而策略改变对象的“内容”。
下一章将讨论与算法封装有关的另一种模式。封装的算法主要用于推迟命令对象的执行。
第 20 章 命 令
何为命令模式
命令对象封装了如何对目标执行指令的信息,因此客户端或调用者不必了解目标的任何细节,却仍可以对它执行任何已有的操作。通过把请求封装成对象,客户端可以把它参数化并置入队列或日志中,也能够支持可撤销的操作。命令对象将一个或多个动作绑定到特定的接收器。命令模式消除了作为对象的动作和执行它的接收器之间的绑定。
命令模式:将请求封装为一个对象,从而可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
何时使用命令模式
- 想让应用程序支持撤销与恢复。
- 想用对象参数化一个动作以执行操作,并用不同命令对象来代替回调函数。
- 想要在不同时刻对请求进行指定、排列和执行。
- 想记录修改日志,这样在系统故障时,这些修改可在后来重做一遍。
- 想让系统支持事务,事务封装了对数据的一系列修改。事务可以建模为命令对象。
命令还能做什么
命令模式允许封装在命令对象中的可执行指令。这使得在实现撤销和恢复基础设施的时候自然会选择这个模式。但这个模式的用途不只如此。命令对象的另一个为人熟知的应用是推迟调用器的执行。调用器可以是菜单项或按钮。使用命令对象连结不同对象之间的操作相当常见,比如,单击视图控制器中的按钮,可以执行一个命令对象,对另一个视图控制器进行某些操作。命令对象隐藏了与这些操作有关的所有细节。
从本章的实例项目中,读者还可以发现另外几个command类,它们使用touchpainter中的其他部分,有着不同的用途。
总结:
本章介绍了命令模式,以及如何在OC中实现这一模式。我们为TouchPainter应用实现了一个撤销基础设施,这样用户就可以撤销和恢复屏幕上所画的线条和点。我们也讲解了CocoaTouch框架如何用不同的调用和撤销/恢复策略实现这个模式,让它能够应用到任何iOS应用程序之中。
消除应用程序中命令、调用器、接收器和客户端的耦合,其好处显而易见。如果特定的命令需要在实现中修改,那么其他组织中的大部分都不受影响。而且添加新的命令类非常容易,因为不必为此修改已有的类。
有关算法封装的这个部分到此结束了。下一个部分将讨论几个与性能和对象访问有关的设计模式。
第八部分 性能 与 对象访问
第21章 享 元
何为享元模式
实现享元模式需要两个关键组件,通常是可共享的享元对象和保存它们的池。某种中央对象维护这个池,并从它返回适当的实例。
何时使用享元模式
- 应用程序使用很多对象;
- 在内存中保存对象会影响内存性能;
- 对象的多数特有状态(外在状态)可以放到外部而轻量化;
- 移除了外在状态之后,可以用较少的共享对象不能替代原来的那组对象;
- 应用程序不依赖对象标识,因为共享对象不能提供唯一的标识。
通过享元对象能够节省的空间,取决于几个因素:
- 通过共享减少的对象总数;
- 每个对象中内在状态的数量;(即,可共享的状态)
- 外在状态是计算出来的还是保存的。
然而,对享元对象外在状态的传递、查找和计算,可能产生运行时的开销,尤其在外在状态原本是作为内在状态来保存的时候。当享元的共享越来越多时,空间的节省会抵消这些开销。共享的享元越多,节省的存储就越多。节省直接跟共享的状态相关。如果对象有大量内在和外在状态,外在状态又能够计算出来而不用存储的时候,就能节省最大的空间。这样我们以两种方式节省了存储空间:共享减少了内在状态的开销,通过牺牲计算时间又节省了外在状态的存储空间。
总结:
分享是人类的美德。分享相同的资源以执行任务,可能比使用个人的资源完成同样的事情更加高效。本章,我们设计了一个可以在屏幕上显示500多朵花的程序,而只用了6个不同花朵图案的实例。这些不同的花朵实例,把一些与众不同的可被标识的信息去掉,只剩下显示花朵图案的基本操作。在请求特定的花朵的时候,客户端需要向花朵实例提供某些与众不同的信息(外在状态),让它使用这些信息绘制一朵与众不同的花。不用享元模式的话,要在屏幕上画多少朵花,程序就需要实例化多少个UIImageView。经过精心的设计,享元模式可以通过共享一部分必需的对象,来节省大量的内存。
在下一章将介绍另一种设计模式,它通过把操作推迟给代理对象来提升对象访问。
第22章 代 理
何为代理模式:
有以下几种代理:
- 远程代理(remote proxy) 为位于不同地址空间或网络上的对象提供本地代表;
- 虚拟代理(virtual proxy) 根据需要创建重型对象;
- 保护代理(protection proxy) 根据各自访问权限控制对原对象的访问;
- 智能引用代理(smart-reference proxy) 通过对真正对象的引用进行计数来管理内存。也用于锁定真正对象,让其他对象不能对其进行修改。
代理模式: 为其他对象提供一种代理以控制对这个对象的访问。
何时使用代理模式
- 需要一个远程代理,为位于不同地址空间或网络中的对象提供本地代表;
- 需要一个虚拟代理,来根据要求;
- 需要一个保护代理,来根据不同访问权限控制对原对象的访问;
- 需要一个智能引用代理,通过对实体对象的引用进行计数来管理内存。也能用于锁定实体对象,让其他对象不能修改它。
总结:
在iOS应用开发中,总是要关注内存的使用量。不论应用程序运行在何种iOS设备上,出于性能的考虑,总是推荐懒加载技术。如果大开销的对象在收到请求之前不需要加载,则可通过虚拟代理向客户端提供某些轻量的信息。
下一个部分将介绍另一种设计模式,它会对对象状态的保存过程进行抽象。
第九部分 对象状态
第23章 备忘录
备忘录模式: 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就将该对象恢复到原先保存的状态。
何时使用备忘录模式
当同时满足以下两个条件时,应该考虑使用这一模式:
- 需要保存一个对象(或某部分)在某一个时刻的状态,这样以后就可以恢复到先前的状态;
- 用于获取状态的接口会暴露实现的细节,需要将其隐藏起来。
总结:
本章讨论了备忘录模式概念,并通过touchpainter实例应用,示范了如何将这一模式应用到iOS应用的开发中。通过保存对scribble对象内部状态的小修改的一个列表,scribblememento也能够被复用于实现撤销与恢复操作。只保存了对scribble的小修改的scribblememento对象列表,可用于在网络上共享线条。这样共享相同绘图会话的远程用户,就能看到模拟的远程绘图,一次画一条线。
到这里本书中介绍设计模式的所有章节就都结束了。现在,读者应该能够在实际项目中自由应用这些设计模式了。