第一部分 认 识 设 计 模 式
第1章 认识模型
设计模式是为特定场景下的问题而定制的解决方案。特定场景指问题所在的重复出现的场景。问题指特定环境下你想要达到的目标。同样的问题在不同的环境下会有不同的限制和挑战。定制的解决方案是指在特定环境下克服了问题的限制条件而达成目标的一种设计。
设计模式的起源——模型、试图和控制器
在MVC设计模式中,对象在应用程序中被氛围三组,分别扮演模型、试图和控制器。MVC模式也定义了对象之间跨越其角色的抽象边界的通信方式。MVC对Cocoa Touch应用程序设计起了重要作用。应用程序设计的一个主要步骤是决定对象或类应该属于这三组中的哪一组。
在模型对象中封装数据和基本行为
模型对象维护应用程序的数据,并定义操作数据的特定逻辑。模型对象可以复用,因为它表示的知识适用于特定的问题领域。例如,模型对象可以表示复杂数据结构,对应用用户在屏幕上所画的图形,或者仅仅表示待办事项应用程序中的一条待办事项。
只要加载的是包含有应用程序永久信息的数据,就应将其放入模型对象。理想状况下,模型对象同用于对其进行显示和边界的用户界面之间不应有任何直接的关联。
使用试图对象向用户展示信息
试图对象可以响应用户操作,并懂得如何讲自己展现在屏幕上。视图对象通常从应用程序的模型对象获取数据用以展示。它可以跟一个模型对象的部分、整体或者多个模型对象合作。通常,用户可用通过它修改数据。
虽然视图对象和模型对象之间关系密切,但是在MVC应用程序中它们之间没有耦合。除非因性能原因(比如视图需要对数据进行缓存),不应将视图用于存储它所展示的数据。
因为视图对象可以与许多不同的模型对象合作,所以它们往往可在不同应用程序之间复用并保持一致。UIKit框架提供了各种类型的视图类,可复用于我们的应用程序。
用控制器对象联系起模型和视图
控制器对象就像视图对象和模型对象的中间人。作为中间人或者协调人,它建立起沟通渠道,使视图得以知晓模型的变更而给予响应。
除了协调作用外,还可以为应用程序管理其他对象的生命周期,进行设置和协调任务。
依照所需的设计,控制器对象可设计为可复用的或不可复用的。
作为复合设计模式的MVC
MVC本身并不是最基本的设计模式,它包含了若干更加基本的设计模式。基本设计模式相互配合,确定了各功能之间的协作,这是MVC应用程序的特性。
Cocoa Touch的MVC用到的模式有:组合、命令、中介者、策略和观察者。
- 组合。视图对象之间以协作的方式构成一个视图层次体系,其中既可以有复合视图,也可以有独立视图(文本框或者按钮)。每个层次的每个视图节点都可以响应用户的操作并把自己绘制到屏幕上。
- 命令。这是一种“目标-动作” 机制,视图对象可以推迟其他对象的执行,让其他对象等到发生了某些事件后再执行。这一机制构成了命令模式。
- 中介者。控制器对象起着中间人的作用,而这个中间人则采用了中介者模式,它构成了模型和视图对象之间传递数据的双向通道。应用程序的控制器对象将模型的变更传达给视图对象。
- 策略。控制器可以是视图对象的一个”策略“。视图对象将自身隔离,以期维持其作为数据展示器的唯一职责,而将一切应用程序特有的界面行为的决定委派给它的”策略“对象(即控制器)。
- 观察者。模型对象向它所关注的控制器等对象发出内部状态变化的通知。
事件传递过程:
1. 用户在画布视图上用手指触摸或拖动,产生一个触摸事件。被触摸的实际视图(图层)就在视图组合中的某个层次上。画布(视图)将触摸消息传达给视图控制器。
2. 控制器对象接收到触摸事件及其相关消息,然后应用策略来变更模型的状态,必要时请求视图对象根据此事件更新其行为或外观。
3. 每当变更发生并已反映到模型对象,模型对象就会通知所有已注册的观察者对象,如控制器。
4. 控制器就想一个协调人,它将变更了的数据从模型传递给视图,以便视图可以相应地更新其外观。
影响设计的几个问题:
设计模式肯定会从许多方面影响系统设计。但是有一些设计原则也会影响设计。有些原则针对一般的软件设计,而有些原则是针对OC和Cocoa Touch的。
针对接口编程,而不是针对实现编程
只要对象符合客户端所要求的接口,客户端就不必在意所使用对象的确切类型;客户端只知道定义接口的协议或者抽象类,因此客户端对对象的类一无所知。在客户端的代码中不声明特定具体类的变量,而只使用协议或抽象类定义的接口。
@protocol与抽象基类
针对接口编程,那么要针对什么样的接口呢?在OC中,有一种称为协议的语言功能@protocol。协议并不定义任何实现,而只声明方法method,以确定符合协议的类的行为。因此,协议是只定义了抽象行为的”接口“。实现协议的类定义这些方法的实现,以执行真正的操作。另一种定义高度抽象类型的方法是定义抽象基类。通过抽象基类,我们可以生成一些其他子类可以共享的默认行为。抽象基类与通常的类相似,只是预留了一些可以或应该由子类重载的行为。
变更过去定义的协议可能会破坏实现该协议的类。协议(或接口)是抽象类型与具体类型之间的一种合约。如果变更合约,所有相关的事项也需要变更。唯一例外是,只使用@optional指令将协议的部分方法变更为”可选的“。而抽象类在接口变更方面要稍微灵活一些。我们可以向抽象基类随意追加新方法,而不会破坏继承链的其他部分。而且,对于子类可能用到的占位方法中定义的默认行为,我们可以随意地进行追加、删除或分解。
对象组合与类继承
类继承或子类化我们可以送其他的类来定义类的实现。
类继承的优缺点:
有点是:
类继承简单直接,因为关系在编译时静态定义;被复用的实现易于修改。
缺点是:因为类继承在编译时定义,所以无法在运行时变更从父类继承来的实现;子类的部分描述常常定义在父类中;子类直接面对父类实现的细节,因此破坏了封装;父类实现的任何变更都会强制子类也进行变更,因为它们的实现联系在了一起;因为在新的问题场景下继承来的实现已过时或不适用,所以必须重写父类或继承来的实现。
由于实现的依存关系,对子类进行复用可能会有问题。有一个解决办法是,只从协议或抽象基类继承(子类型化),因为它们只有很少的实现,而协议则没有实现。
对象组合让我们同时使用多对象,而每个对象都假定其他对象的接口正常运行。因此,为了在系统中正常运行,它们的接口都需要经过精心的设计。然而,对象组合也有其优缺点应予考虑。
优点是:
不会破坏封装,因为只通过接口来访问对象;大大减少实现的依存关系,因为对象的实现是通过接口来定义的;可以在运行时将任意对象替换为其他同类型的对象;有助于保持类的封装以专注于单一任务;类及其层次结构能保持简洁,不至于过度膨胀而无法管理。
缺点是:
设计中涉及较多对象;系统的行为将依赖于不同对象间的关系,而不是定义于单个类中;理想情况下,不需要创建新的组件就能实现复用;十分罕见的情况是,通过对象组合的方式,仅仅对已有的组件进行组合就能得到所需的全部功能;实际上,现成的组件总是不太够用。
优先使用对象组合而不是类继承,并不是说完全不使用类继承。需要根据具体情况对如何复用类和对象作出清晰的判断。如果系统设计得合理,类继承与对象组合可以相互配合。设计类时,通常倾向于考虑对象组合。然后寻找出冗余行为,进行设计细化。如果找到冗余行为,也许意味着此处应该使用类继承。
第2章 案例分析:设计一个应用程序
设计过程中有3个重要的里程碑:
- 想法的概念化
- 界面外观的设计
- 架构设计
2.1 想法的概念化
任何类型的软件开发都要一些需求,但是并不需要在一开始就确定全部细节。从最基本的开始:
. 可以用手指涂鸦的画板
如果只能绘制黑白图形,我觉得任何用户对这种绘图体验都不会满意。要是有设定不同颜色和线条粗细的选项肯定很不错。这样又有了一个需求:
. 用户可以改变线条颜色和粗细
要是用户不能打开保存的涂鸦图并作修改,保存将毫无意义。所以又有了这个需求:
. 允许用户打开保存的涂鸦图
要是用户不喜欢自己的作品,想删除重来怎么办?
. 允许用户删除当前的涂鸦图
要是允许用户撤销与恢复涂鸦肯定很不错。所以又有了这个需求:
. 允许用户撤销和恢复涂鸦
这个清单可以一直列下去,但眼下已经有了可以启动设计的基本需求。但是进入设计阶段以前,应该确定把握了应用程序的界面外观。
2.2 界面外观的设计
界面外观驱动的设计让我们能在一开始就专注于用户体验。这样不只是能够为发布前的最后关头省下时间,而且也能确保利益相关者与开发人员在开发过程中保持一致。即使在开发过程中,做好的应用程序界面外观和UI设计,可以给忙于编码的开发人员很好的视觉提示,告诉他们是在开发什么东西。这样可以提高生产效率,因为可以在初期就发现那些难以定位的bug和设计缺陷。
2.3 架构设计
- 视图管理。 从一个视图到另一个视图的迁移;使用中介者来协调视图迁移。
- 如何表现涂鸦。 在屏幕上可以画“什么”;用组合结构来表示痕迹;绘制涂鸦图。
- 如何表现保存的涂鸦图。 获取涂鸦图的状态;回复涂鸦图的状态。
- 用户操作。浏览涂鸦缩略图的列表;涂鸦图的撤销和恢复;变更线色与线宽;删除屏幕上的当前涂鸦图。
2.3.1 视图管理
使用中介者来协调视图迁移
中介者模式是指用一个对象来封装一组对象之间的交互逻辑。中介者通过避免对象间显示是相互引用来增近不同对象间的松耦合。因此对象间的交互可以集中在一处控制,对象间的依存关系会减少。
2.3.2 如何表现涂鸦
2.3.4 用户操作
所用设计模式的回顾
这一部分我们所学到的设计模式有:中介者、组合、访问者、代理、备忘录、命令、观察者。