第四部分 对 象 去 耦
第11章 中 介 者
面向对象的设计鼓励把行为分散到不同对象中。这种分散可能导致对象之间的相互关联。在最糟糕的情况下,所有对象都彼此了解并相互操作。
虽然把行为分散到不同对象增强了可复用性,但是增加的相互关联又减少了获得的益处。增加的关联使得对象很难或不能在不依赖其他对象的情况下工作。应用程序的整体行为可能难以进行任何重大修改,因为行为分布于许多对象。于是结果可能是创建越来越多的子类,以支持应用程序的任何新行为。
中介者模式用于定义一个集中的场所,对象间的交互可以在一个中介者对象中处理。其他对象不必彼此交互,因此较少了它们之间的依存关系。
中介者模式: 用一个对象来封装一系列对象的交互方式。中介者使各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
何时使用中介者模式
- 对象之间的交互虽然定义明确然而非常复杂,导致一组对象彼此相互依赖而且难以理解;
- 因为对象引用了许多其他对象并与其通讯,导致对象难以复用;
- 想要定制一个分布在多个类中的逻辑或行为,又不想生成太多子类。
管理 TouchPainter 应用程序中的视图迁移
中介者模式不只适用于把各种对象间错综复杂的关系集中化,也适合组织两个不同视图间视图迁移。通过把一个视图加到另一个视图之上来管理视图迁移的iOS应用程序相当常见。这样第一个视图需要知道第二个视图并保持对它的引用,然后是第三个,依次类推。
说明: 中介者模式以中介者内部的复杂性代替交互的复杂性。因为中介者封装与合并了colleague(同事)的各种协作逻辑,自身可能变得比它们任何一个都复杂。这会让中介者本身成为无所不知的庞然大物,并且难以维护。
总结: 本章探讨了与中介者模式有关的很多内容,也探讨了如何使用cocoa touch框架用oc来实现这一模式。
虽然对于处理应用程序的行为分散于不同对象并且对象相互依存的情况,中介者模式非常有用,但是应该注意避免让中介者类过于庞大而难以维护。如果已经这样子了,可以考虑使用另一种设计模式把它分解。要创造性地混用和组合各种设计模式解决同一个问题。每个设计模式就像一个乐高积木块。整个应用程序可能要使用彼此配合的各种“积木块”来创造。
在下一章,将会讨论另一种设计模式,它使用一种“发布-订阅”机制来消除对象耦合。
第12章 观 察 者
观察者模式: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
何时使用观察者模式
- 有两种抽象类型相互依赖。将它们封装在各自的对象中,就可以对它们单独进行改变和复用。
- 对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变。
- 一个对象必须通知其他对象,而它又不需要知道其他对象是什么。
在如下两处地方使用观察者模式:
1.在模型-视图-控制器中使用观察者模式
2. 在Cocoa Touch框架中使用观察者模式
cocoa touch 框架用两种技术改写了观察者模式——通知和键值观察(kvo)。
2.1 通知
cocoa touch框架使用NSNotiticationCenter和NSNotification对象实现了一对多的发布-订阅模型。它们允许主题与观察者以一种松耦合的方式同学。两者在通讯时对另一方无需多少了解。
主题要通知其他对象时,需要创建一个可通过全局的名字来识别的通知对象,然后把它投递到通知中心。通知中心查明特定通知的观察者,然后通过消息把通知发送给它们。对象订阅了特定类型的通知时,需要通过选择器提供一个方法的名字。这个方法必须符合一种单一参数的签名。方法的参数是通知对象,它包含通知名称、被观察的对象以及带有任何补充信息的字典。当有通知到来时,这个方法会被调用。
模型对象在内部数据改变之后,能够把通知投递到通知中心,使消息能够广播给其他正在观察的对象,然后这些对象可作出适当的响应。模型可以像下面这样构造一个通知然后投递到通知中心:
NSNotification *notification = [NSNotification notificationWithName:@"data changes" object: self];
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter postNotification:notification];
通知的实例可以用NSNotification类的类工厂方法,通过指定通知名和作为传给观察者的参数的任何对象来创建。在前面的例子中,通知名是@“data changes”。确切的名字随实现而不同。如果主题要传递自身作为对象参数,可在创建过程中指定self来实现。
一旦创建了通知,就用它作为[notificationCenter postNotification:notification];消息调用的参数,投递到通知中心。通过向NSNotificationCenter类发送defaultCenter消息,可以得到NSNotificationCenter实例的引用。每个进程只有一个默认的通知中心,所以默认的NSNotificationCenter是个单例对象。defaultCenter是返回应用程序中NSNotificationCenter的唯一默认实例的工厂方法。
任何要订阅这个通知的对象,首先需要为自己进行注册。如下面的代码段所示:
[notificationCenter addObserver:self selector:@selector(update:) name:@"data changes" object:subject];
notificationCenter是用与主题投递通知的步骤里相同的方法得到的。要注册观察者,进行观察的对象需要在addObserver消息调用中把self注册为观察者。它也需要指定选择器,用以识别在通知中心通知这个作观察对象时被调用的方法。对收到通知时被调用的方法,作观察的对象也可以选择设定所关心的通知的名字,已经任何其他对象作为参数。通知中心用提供的信息来确定应该想做观察的对象分发何种通知。就我们的例子来说,诶了接受同一个通知,至少它需要指定同样的通知名。
键 - 值 观 察
cocoa 提供了一种称为键值观察的机制,对象可以通过它得到其他对象特定属性的变更通知。这种机制在模型-视图-控制器模式的场景中尤其重要,因为它让视图对象可以经由控制器层观察模型对象的变更。
这一机制基于NSKeyValueObserving非正式协议,cocoa通过这个协议为所有遵守协议的对象提供了一种自动化的属性观察能力。要实现自动观察,参与键-值观察的对象需要符合键-值编码的要求,并且需要符合KVC的存取方法。KVC基于有关非正式协议,通过存取对象属性实现自动观察。也可以使用NSKeyValueObserving的方法和相关范畴来实现手段的观察者通知。对于手动实现,可以禁止默认的自动通知,也可以两者都保留。
通知和键值观察都是cocoa对观察者模式的改写。尽管两者都依赖同样的发布者-订阅者关系,但是它们是为不同的解决方案而设计的。两者的区别:
通知: 一个中心对象为所有观察者提供变更通知;主要从广义上关注程序事件;
键值观察:被观察的对象直接向观察者发送通知;绑定于特定对象属性的值;
现在通过TouchPainter 应用程序,看看如何使用KVO把变更通知从模型(经由控制器)传递到视图
在TouchPainter中更新canvasView上的线条
CanvasViewController: 控制器 scribble: 模型 canvasView: 视图
因为CanvasViewController的scribble属性是自动合成的,所以同通常不需要为他提供任何定制的存取方法。但是事情是这样的: CanvasViewController依靠scribble发来的更新通知,以进一步指示器canvasView如何画或重画scribble中的Mark。它需要用下面的消息调用,将自己加为其私有成员变量scribble的观察者:
[scribble_ addObserver:self forKeyPath:@"mark" options: NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil];
NSKeyValueObservingOptionInitial选项让scribble_通知CanvasViewController,在这个消息调用之后立刻提供其mark属性的初始值。这个选项很重要,因为当scribble对象在其init*方法中进行初始化,第一次设置mark属性时,CanvasViewController也需要接收通知。NSKeyValueObservingOptionNew选项指示scribble_,每当其mark属性被设定了新值时通知CanvasViewController。context参数指定可选的对象作为通知的参数。好了,我们回到消息调用本身。问题是,应该把它放在哪儿呢?如果只放在viewDidLoad方法中,那么当客户端把别的Scribble实例赋给控制器的时候,观察连接就会断开。所以它们之间建立连接最好的地方是在scribble_的set存取方法中。它就像一道关口,防止观察连接被破坏的任何可能。同时,在把别的Scribble引用赋给CanvasViewController之后,存取方法会发一个消息给canvasView_,让它用新的Scribble引用赋给CanvasViewController之后,存取方法会发一个消息给canvasView_,让它用新的Scribble中的mark重画。
所以,设置CanvasViewController与其scribble_之间的观察连接的部分,并没有放在viewDidLoad方法中,而是每当控制器被加载时,就在那里创建第一个Scribble实例,并且使用存取方法进行赋值。
如果使用前面几节中讨论的NSNotification和NSNotificationCenter代替KVO来实现同样功能的话,会是下面这个样子。
- 需要为主题和观察者(scribble_和canvasViewController)定义一个共同的标识符。
- 当scribble_的内部状态发生改变时,它会把带有指定标识符的通知,使用任何必要的对象作为参数(NSNotification的实例)投递到NSNotificationCenter。
- 接下来,所有订阅了标有这个标识符的通知的注册观察者,会从NSNotificationCenter收到这一消息。
- 然后观察者会在作为回调函数提供给NSNotification的选择其中处理这一通知。除了使用 框架提供的这两种方式之外,有些人也许想从零开始构建自己的观察者基础设施。
总结:
本章讨论了观察者模式的背景信息以及这一模式的用途。本章也讨论了如何在TouchPainter应用程序中为其模型-视图- 控制器架构实现这一模式,在用户用手绘图时把线条的变更反映到屏幕上。
我们也可以不必从头开始实现整个方案,而是利用cocoa touch框架中使用键值观察(KVO)以及NSNotification和NSNotificationCenter对象实现好的观察者模式。
在下一个部分,我们将讨论几个用于形成抽象集合结构的设计模式,以及与它们的行为直接相关的其他几个模式。
网友评论