原文地址:
在Cocoa中到处都可以找到设计模式的应用,基于模式的机制或架构在Cocoa框架和Objective-C运行环境及语言中是很常见的。Cocoa经常把自己与众不同的工作机制建立在模式上,它的设计受到诸如语言能力或现有架构这样因素的影响。
本部分包含设计模式:可重用的面向对象软件的元素一书中编目的大多数设计模式的介绍。每个设计模式都有一个总结性的描述,以及该模式的Cocoa实现的讨论。文中列出的都是Cocoa实现的模式,每个模式的讨论都发生在特定的Cocoa环境中。我们推荐您熟悉这些模式,您会发现这些模式在Cocoa软件开发中非常有用。
Cocoa中设计模式的实现有不同的形式。下面部分中描述的一些设计—比如协议和范畴—是Objective-C语言的特性;在另外一些场合中,“模式的实例”被实现为一个类或一组相关的类(比如类簇和单件类);还有一些场合下,模式表现为一个大的框架结构,比如响应者链模式。对于某些基于模式的机制,您几乎可以“免费”使用;而另外一些机制则要求您做一些工作。即使对于Cocoa没有实现的模式,我们也鼓励您在条件许可的情况下自行实现,比如在扩展类的行为时,对象合成(装饰模式)技术通常就比生成子类更好。
有两个设计模式没有在下面的内容中进行讨论,即模型-视图-控制器(MVC)模式和对象建模。MVC是一种复合或聚合模式,就是说它基于几种不同类型的模式。对象建模在四人组的分类目录中没有对应类别,它源自关系数据库领域。然而,MVC和对象建模在Cocoa中可能是最重要和最普遍的设计模式或用语,而且它们在很大程度上是相关的。它们在几个技术的设计中发挥关键的作用,包括绑定、撤消管理、脚本控制、和文档架构。要了解更多有关这些模式的信息,请参见"模型-视图-控制器设计模式"和"对象建模"部分。
抽象工厂模式
适配器模式
责任链模式
命令模式
合成模式
装饰模式
表观模式
跌代器模式
仲裁者模式
备忘录模式
观察者模式
代理模式
单件模式
模板方法模式
提供一个接口,用于创建与某些对象相关或依赖于某些对象的类家族,而又不需要指定它们的具体类。通过这种模式可以去除客户代码和来自工厂的具体对象细节之间的耦合关系。
类簇是一种把一个公共的抽象超类下的一些私有的具体子类组合在一起的架构。抽象超类负责声明创建私有子类实例的方法,会根据被调用方法的不同分配恰当的具体子类,每个返回的对象都可能属于不同的私有子类。
Cocoa将类簇限制在数据存储可能因环境而变的对象生成上。Foundation框架为NSString
、NSData
、NSDictionary
、NSSet
、和NSArray
对象定义了类簇。公共超类包括上述的不可变类和与其相互补充的可变类NSMutableString
、 NSMutableData
、NSMutableDictionary
、NSMutableSet
、和NSMutableArray
。
当您希望创建类簇代表的类型的可变或不可变对象时,可以使用类簇中的某个公共类来实现。使用类簇是在简洁性和扩展性之间进行折衷。类簇可以简化类接口,因此使其更易于学习和使用,但是创建类簇抽象类的定制子类则会变得更加困难。
将一个类接口转化为客户代码需要的另一个接口。适配器使原本由于兼容性而不能协同工作的类可以工作在一起,消除了客户代码和目标对象的类之间的耦合性。
协议是一个编程语言级别(Objective-C)的特性,它使定义适配器模式的实例成为可能(在 Java中的“接口”和“协议”是同义的)。如果您希望一个客户对象和另一个对象进行交流,但由于它们的接口不兼容而导致困难,您就可以定义一个协议,它本质上是一系列和类不相关联的方法声明。这样,其它对象的类就可以正式采纳该协议,并通过实现协议中的全部方法来“遵循”该协议。结果,客户对象就可以通过协议接口向其它对象发送消息。
协议是一组独立于类层次的方法声明,这样就有可能象类的继承那样,根据对象遵循的协议对其进行分组。您可以通过NSObject
的conformsToProtocol:
方法来确认一个对象的协议关系。
除了正式协议之外,Cocoa还有一个非正式协议的概念。这种类型的协议是NSObject
类中的一个范畴(category),这样就使所有的对象都成为范畴方法的潜在实现者(参见"范畴"部分)。非正式协议的方法可以选择性地实现。非正式协议是委托机制实现的一部分(参见"委托"部分。
请注意,协议的设计和适配器模式并不完全匹配。但它是使接口不兼容的类在得以协同工作的手段。
协议主要用于声明层次结构上不相关的类为了互相通讯而需要遵循的接口。但是,您也可以将协议用于声明对象的接口,而隐藏相应的类。Cocoa框架包括很多正式协议,这些协议使定制子类可以和框架进行特定目的的通讯。举例来说,Foundation框架中包含NSObject
、NSCopying
、和NSCoding
协议,都非常重要。Application Kit中的协议包括NSDraggingInfo
、NSTextInput
、和NSChangeSpelling
。
正式协议要求遵循者实现协议声明的所有方法。它们也是零碎的,一旦您定义一个协议并将它提供给其它类,将来对协议的修改会使那些类不能工作。
通过为多个对象提供处理请求的机会,避免请求的发送者和接收者产生耦合。将接收对象串成链,并将请求沿着接收者链进行传递,直到某个对象对其进行处理。对象或者处理该请求,或者将它传递给链中的下一个对象。
Application Kit框架中包含一个称为响应者链的架构。该链由一系列响应者对象(就是从NSResponder
继承下来的对象)组成,事件(比如鼠标点击)或者动作消息沿着链进行传递并(通常情况下)最终被处理。如果给定的响应者对象不处理特定的消息,就将消息传递给链中的下一个响应者。响应者在链中的顺序通常由视图的层次结构来决定,从层次较低的响应者对象向层次较高的对象传递,顶点是管理视图层次结构的窗口对象,窗口对象的委托对象,或者全局的应用程序对象。事件和动作消息在响应者链中的确切传递路径是不尽相同的。一个应用程序拥有的响应者链可能和它拥有的窗口(甚至是局部层次结构中的视图对象)一样多,但每次只能有一个响应者链是活动的—也就是与当前活动窗口相关联的那个响应链。
还有一个与响应者链相类似的链,用于应用程序的错误处理。
视图层次的设计应用的是合成模式(参见"合成模式"部分),它和响应者链密切相关。动作消息—源自控件对象的消息—基于目标-动作机制,是命令模式(参见"命令模式"部分)的一个实例。
当您通过Interface Builder或以编程的方式为程序构造用户界面时,可以“免费”得到一个或多个响应者链。响应者链和视图层次结构一起出现,当您使一个视图对象成为窗口内容视图的子视图时,视图层次结构就自动生成了。如果您将一个定制视图加入到一个视图层次结构中,它就变成响应者链的一部分;如果您实现了恰当的NSResponder
方法,就可以接收和处理事件及动作消息。定制对象是窗口对象或全局应用程序对象NSApp的委托对象,也可以接收和处理那些消息。
您也可以用编程的方式将定制的响应者对象注入到响应者链中,以及通过编程操作响应者在链中的顺序。
进一步阅读: 处理事件和动作消息及程序错误的的响应者链在Cocoa事件处理指南和Cocoa的错误处理编程指南文档中进行描述。本文档在"合成模式"部分对视图层次结构有总结性的介绍,在"核心应用程序架构"部分有更全面的描述。
这种模式将请求封装为对象,使您可以用不同的请求来对客户代码进行参数化;对请求进行排队和记录,并支持可撤消(undoable)的操作。请求对象将一或多个动作绑定在特定的接收者上。命令模式将发出请求的对象和接收及执行请求的对象区分开来。
NSInvocation
类的实例用于封装Objective-C消息。一个调用对象中含有一个目标对象、一个方法选择器、以及方法参数。您可以动态地改变调用对象中消息的目标及其参数,一旦消息被执行,您就可以从该对象得到返回值。通过一个调用对象可以多次调用目标或参数不同的消息。
创建NSInvocation
对象需要使用NSMethodSignature
对象,该对象负责封装与方法参数和返回值有关系的信息。NSMethodSignature
对象的创建又需要用到一个方法选择器。NSInvocation
的实现还用到Objective-C运行环境的一些函数。
NSInvocation
对象是分布式、撤消管理、消息传递、和定时器对象编程接口的一部分。在需要去除消息发送对象和接收对象之间的耦合关系的类似场合下,您也可以使用。
分布式对象是一种进程间通讯技术,关于这个主题的更多信息请参见"代理模式"部分。
目标-动作机制使控件对象—也就是象按键或文本输入框这样的对象—可以将消息发送给另一个可以对消息进行解释并将它处理为具体应用程序指令的对象。接收对象,或者说是目标,通常是一个定制的控制器对象。消息—也被称为动作消息—由一个选择器来确定,选择器是一个方法的唯一运行时标识。典型情况下,控件拥有的单元对象会对目标和动作进行封装,以便在用户点击或激活控件时发送消息(菜单项也封装了目标和动作,以便在用户选择时发送动作消息)。目标-动作机制之所以能够基于选择器(而不是方法签名),是因为Cocoa规定动作方法的签名和选择器名称总是一样的。
当您用Interface Builder构建程序的用户界面时,可以对控件的动作和目标进行设置。您因此可以让控件具有定制的行为,而又不必为控件本身书写任何的代码。动作选择器和目标连接被归档在nib文件中,并在nib文件被解档时复活。您也可以通过向控件或它的单元对象发送setTarget:
和setAction:
消息来动态地改变目标和动作。
目标-动作机制经常用于通知定制控制器对象将数据从用户界面传递给模型对象,或者将模型对象的数据显示出来。Cocoa绑定技术则可以避免这种用法,有关这种技术的更多信息请参见Cocoa绑定编程主题文档。
Application Kit中的控件和单元并不保持它们的目标。相反,它们维护一个对目标的弱引用。进一步的信息请参见"委托、观察者、和目标的所有权" 部分。
这种模式将互相关联的对象合成为树结构,以表现部分-全部的层次结构。它使客户代码可以统一地处理单独的对象和多个对象的合成结果。
一个窗口包含的视图对象(NSView
对象)在内部构成了一个视图层次结构。层次结构的根是窗口对象(NSWindow
对象)和它的内容视图。内容视图就是填充到窗口内容方框中的透明视图,添加到内容视图中的视图都是它的子视图,而这些子视图又会成为下一级视图的父视图。一个视图有一个(且只有一个)父视图,可以有零或多个子视图。在视觉上,您可以将这个结构理解为包含关系:父视图包含它的子视图。图4-2显示视图层次的结构以及在视觉上的关系。
视图层次是一个结构方面的架构,在图形描画和事件处理上都扮演一定的角色。一个视图有两个影响图形操作位置的边界框,即边框(frame)和边界(bound)。边框是外部边界,表示视图在其父视图坐标系统中的位置,它负责定义视图的尺寸,并根据视图边界对图形进行裁减。边界则是内部的边界框,负责定义视图对象自身描画表面的内部坐标系统。
当窗口系统要求一个窗口做好显示准备时,父视图会在其子视图之前被要求进行渲染。当您向一个视图发送消息时—比如发送一个重画视图的消息—该消息就会被传播到子视图。因此,您可以将视图层次结构中的一个分支当成一个统一的视图来处理。
响应者链也把视图层次用于处理事件和动作消息。请参见责任链模式部分中("责任链模式")关于响应者链的总结描述。
无论以编程的方式,还是通过Interface Builder,当您将一个视图加入到另一个视图中时,都需要创建或修改视图层次结构。Application Kit框架自动处理与视图层次结构相关联的所有关系。
这种模式动态地将额外的责任附加到一个对象上。在进行功能扩展时,装饰是子类化之外的一种灵活的备选方法。和子类化一样,采纳装饰模式可以加入新的行为,而又不必修改已有的代码。装饰将需要扩展的类的对象进行包装,实现与该对象相同的接口,并在将任务传递给被包装对象之前或之后加入自己的行为。装饰模式表达了这样的设计原则:类应该接纳扩展,但避免修改。
装饰是用于对象合成的模式。在您自己的代码中应该鼓励使用对象的合成(参见"什么时候需要生成子类"部分)。然而,Cocoa自己提供了一些基于这种模式的类和机制(在下面的部分进行讨论)。在这些实现中,扩展对象并不完全复制它所包装的对象的接口,虽然具体实现中可以使用不同的技术来进行接口共享。
Cocoa在实现某些类时用到了装饰模式,包括NSAttributedString
、NSScrollView
、和NSTableView
。后面两个类是复合视图的例子,它们将其它一些视图类的对象组合在一起,然后协调它们之间的交互。
委托是在宿主对象中嵌入一个指向另一对象(也就是委托对象)的弱引用(一个未保持的插座变量),并不时地向该委托对象发送消息,使其对有关的任务进行输入的机制。宿主对象一般是一个“复活”的框架对象(比如一个NSWindow
或NSXMLParser
对象),它寻求完成某项工作,但又只能以一般的方式来进行。委托几乎总是一个定制类的实例,它负责配合宿主对象,在有关任务的特定点(参见图4-3)上提供与具体程序有关的行为。这样,委托机制使我们可以对另一个对象的行为进行修改或者扩展,而不需要生成子类。
简而言之,委托就是一个对象将任务委托给另一个对象,它是面向对象编程的常用技术。然而,Cocoa以独特的方式实现委托机制。宿主类用非正式协议—即NSObject
中的范畴—来定义委托对象可能选择实现的接口。委托对象不必象采纳正式协议那样实现所有的协议方法。在向委托对象发送消息之前,宿主对象可以首先确定相应的方法是否实现(通过respondsToSelector:
消息),以避免运行时例外。有关正式协议和非正式协议的更多信息,请参见"协议"部分。
Cocoa框架中的一些类也向它们的数据源发送消息。数据源在各个方面都和委托一样,除了它的目的是为宿主对象提供数据,以传递给浏览器、表视图、或者类似的用户界面视图。和委托不同的是,数据源还必须实现某些协议方法。
委托不是装饰模式的严格实现。宿主(委托)对象并没有包装它希望扩展的类的实例。相反,委托是对委托框架类的行为进行特殊化。除了框架类声明的委托方法之外,它们也没有公共的接口。
Cocoa中的委托也是模板方法模式("模板方法模式")的一部分。
委托是Cocoa框架中的一种常用的设计。Application Kit框架中的很多类都向它们的委托发送消息,包括NSApplication
、NSWindow
、和NSView
的几个子类。Foundation框架中的一些类,比如NSXMLParser
和NSStream
,也维护自己的委托。您应该总是使用类的委托机制,而不是生成类的子类,除非委托方法不能完成您的目标。
虽然您可以动态地改变委托,但是同时只能有一个对象可以成为委托。因此,如果您希望当特定的程序事件发生时,有多个对象可以同时得到通知,则不能使用委托。在这种情况下您可以使用通告机制。如果委托对象实现了一或多个框架类声明的通告方法,就会自动接收到其委托框架对象的通告。请参考观察者模式( "观察者模式")中有关通告的讨论。
Application Kit框架中的向外委托任务的对象并不保持它们的委托或数据源,而是维护一个弱引用,更多信息请参见"委托、观察者、和目标的所有权"部分。
范畴是Objective-C语言的一个特性,用于为一个类增加方法(接口和实现),而不必生成子类。类原始声明的方法和通过范畴添加的方法在运行时没有区别—在您的程序的作用范围内。范畴中的方法成为类类型的一部分,并被所有的子类继承。
和委托一样,范畴并没有严格适配装饰模式。它实现了该模式的目的,但采用不同的实现方式。范畴加入的行为是在编译时生成的,而不是动态得到的。而且,范畴并没有封装被扩展的类的实例。
Cocoa框架中定义了很多范畴,大多数都是非正式协议(在"协议"部分中进行总结)。它们通常使用范畴来对相关的方法进行分组。您也可以在代码中实现范畴,以在不生成子类的情况下对类进行扩展,或者对相关的方法进行分组。但是您需要注意如下两点:
您不能为类添加实例变量。
如果您对现有的方法进行重载,则应用程序可能产生预料之外的行为。
这种模式为子系统中的一组接口提供统一的接口。表观模式定义一个更高级别的接口,通过减少复杂度和隐藏子系统之间的通讯和依赖性,使子系统更加易于使用。
NSImage
类为装载和使用基于位图(比如JPEG、PNG、或者TIFF格式)或向量(EPS或PDF格式)的图像提供统一的接口。NSImage
可以为同一个图像保持多个表示,不同的表示对应于不同类型的NSImageRep
对象。NSImage
可以自动选择适合于特定数据类型和显示设备的表示。同时,它隐藏了图像操作和选择的细节,使客户代码可以交替使用很多不同的表示。
由于NSImage
支持几种不同的图像表示,因此某些属性可能不能适用。举例来说,您可能不能取得图像中一个像素的颜色,如果潜在的图像表示使基于向量且与设备无关的话。
这种模式提供一种顺序访问聚合对象(也就是一个集合)中的元素,而又不必暴露潜在表示的方法。迭代器模式将访问和遍历集合元素的责任从集合对象转移到迭代器对象。迭代器定义一个访问集合元素的接口,并对当前元素进行跟踪。不同的迭代器可以执行不同的遍历策略。
Foundation框架中的NSEnumerator
类实现了迭代器模式。NSEnumerator
抽象类的私有具体子类返回的枚举器对象可以顺序遍历不同类型的集合—数组、集合、字典(值和键)—并将集合中的对象返回给客户代码。
NSDirectoryEnumerator
是一个不紧密相关的类,它的实例可以递归地枚举文件系统中目录的内容。
象NSArray
、NSSet
、和NSDictionary
这样的集合类都包含相应的方法,可以返回与集合的类型相适用的枚举器。所有的枚举器的工作方式都一样。您可以在循环中向枚举器发送nextObject
消息,如果该消息返回nil
,而不是集合中的下一个对象,则退出循环。
这种模式定义的对象用于封装一组对象的交互机制。仲裁者模式可以避免对象之间显式的互相引用,使对象之间的耦合变得宽松,也使您可以独立地改变它们的交互方式。这些对象也因此可以更具重用性。
仲裁者对象集中了系统中的对象之间的复杂通讯和控制逻辑。这些对象在状态发生改变时会告诉仲裁者对象,反过来,也对仲裁者对象的请求进行响应。
模型-视图-控制器设计模式为一个面向对象的系统(比如一个应用程序)中的对象分配不同的角色。它们可以是模型对象,包含应用程序的数据及对那些数据进行操作;可以是视图对象,负责表示数据及响应用户动作;也可以是控制器对象,负责协调模型和视图对象。控制器对象适合于仲裁者模式。
在Cocoa中,控制器对象一般有两个类型:仲裁控制器或者协调控制器。仲裁控制器负责仲裁应用程序中视图对象和模型对象之间的数据流。仲裁控制器通常是NSController
对象。协调控制器则负责实现应用程序的集中化通讯和控制逻辑,作为框架对象的委托和动作消息的目标。它们通常是NSWindowController
对象或定制NSObject
子类的实例。由于协调控制器高度专用于特定的程序,因此不考虑重用。
Application Kit框架中的NSController
抽象类和它的具体子类是Cocoa绑定技术的一部分,该技术可以自动同步模型对象包含的数据和视图对象中显示及编辑的数据。举例来说,如果用户在一个文本框中编辑一个字符串,绑定技术会将文本框中的变化(通过仲裁控制器)传递给绑定了的模型对象中合适的属性。编程者需要做的就是正确设计自己的模型对象,并通过Interface Builder在程序的视图、控制器、和模型对象之间建立绑定关系。
具体的公共控制器类的实例可以在Interface Builder的控件选盘上得到,因此是高度可重用的。它们提供一些服务,比如选择和占位符值的管理。这些对象执行下面这些特定的功能:
NSObjectController
管理一个单独的模型对象。
NSArrayController
管理一个模型对象数组,以及维护一个选择;还可以在数组中加入或删除模型对象。
NSTreeController
使您可以在一个具有层次的树结构中添加、删除、和管理模型对象。
NSUserDefaultsController
为预置(用户缺省值)系统提供一个便利的接口。
您通常可用将NSController
对象用作仲裁控制器,因为这些对象的设计目的是在应用程序的视图对象和模型对象之间传递数据。在使用仲裁控制器时,您通常是从Interface Builder选盘中拖出对象,指定模型对象的属性键,并通过Interface Builder Info 窗口中的Bindings面板建立视图和模型对象之间的绑定关系。您也可以生成NSController
或其子类的子类,以获得更具具体行为的子类。
几乎任何一对对象之间都可以建立绑定关系,只要它们遵循NSKeyValueCoding
和NSKeyValueObserving
这两个非正式协议。但是,我们推荐您通过仲裁控制器来建立绑定,以得到NSController
及其子类为您提供的各种好处。
协调控制器通过下面的方式集中实现一个应用程序的通讯和控制逻辑:
维护指向模型和视图对象的插座变量(插座变量是指向其它被保持为实例变量的连接或引用)。
通过目标-动作机制响应用户在视图对象上的操作(参见"目标-动作"部分)。
作为委托对象,处理从框架对象发出的消息(参见"委托"部分)。
您通常可以在Interface Builder中建立上述的连接—插座变量、目标-动作、和委托,它将这些连接归档到应用程序的nib文件中。
这种模式在不破坏封装的情况下,捕捉和外部化对象的内部状态,使对象在之后可以回复到该状态。备忘录模式使关键对象的重要状态外部化,同时保持对象的内聚性。
归档将一个程序中的对象以及对象的属性(包括属性和关系)存储到档案上,使之可以保存到文件系统中,或者在不同的处理器和网络间传递。档案将程序的对象图保存为独立于架构的字节流,对象的标识和对象之间的关系都会被保留。由于对象的类型和它的数据一起被存储,从归档的字节流解码出来的对象会被正常实例化,实例化所用的类与原来编码的类相同。
通常情况下,您希望将程序中需要保存状态的对象归档。模型对象几乎总是属于这个范畴。您通过编码将对象写入到档案中,而通过解码将对象从档案中读取出来。通过NSCoder
对象可以执行编解码操作,在编解码过程中最好使用键化的归档技术(需要调用NSKeyedArchiver
和NSKeyedUnarchiver
类的方法)。被编解码的对象必须遵循NSCoding
协议;该协议的方法在归档过程中会被调用。
属性列表是一个简单的、具有一定结构的对象图序列,它仅使用下面这些类的对象:NSDictionary
、NSArray
、NSString
、NSData
、NSDate
、和NSNumber
。这些对象通常也被称为属性列表对象。Cocoa中有几个框架类提供了序列化属性列表对象,以及定义录写对象内容及其层次关系的特殊数据流格式的方法。NSPropertyListSerialization
类就提供了将属性列表对象序列化为XML或其它优化的二进制格式的类方法。
如果对象图中包含的是简单对象,则在捕捉和外部化对象及其状态时,属性列表序列化是一种灵活的、可移植的、而又非常适当的工具。然而,这种形式的序列化有它的限制,它不保留对象的全部类标识,而只保留一些一般的类型(数组、字典、字符串、等等)。这样,从属性列表恢复出来的对象可能和原来的类不同,特别是当对象的可变性可能发生变化时,这就会带来问题。属性列表序列化也不跟踪在同一对象中被多次引用的对象,这可能导致反向序列化时产生多个实例,而在原来的对象图中却只有一个实例。
Core Data是一个管理对象图,并使其留存的框架和架构。正是第二种能力—对象的留存能力—使Core Data成为备忘录模式的一种适配形式。
在Core Data架构中,中心的对象称为被管理对象上下文,负责管理应用程序对象图中的模型对象。在被管理对象上下文下面是该对象图的持久栈,也就是一个框架对象的集合,负责协调模型对象和外部数据存储,比如XML文件或关系数据库。持久栈对象负责建立存储中的数据和被管理对象上下文中的对象之间的映射关系,在有多个数据存储的时候,持久栈对象将这些存储表现为被管理对象上下文中的一个聚合存储。
Core Data的设计也在很大程度上受到模型-视图-控制器以及对象建模模式的影响。
Core Data在开发企业级应用程序时特别有用,这些程序需要定义、管理、以及从数据存储中透明地归档和解档复杂模型对象图。Xcode开发环境中包含有关的工程模板和设计工具,在创建两种一般类型的Core Data应用程序(即基于文档和非基于文档的类型)时,这些模板和工具可以节省一些编程的工作量。Interface Builder也在其选盘中包含可配置的Core Data框架对象。
进一步阅读: 在本文档中,"其它Cocoa架构"部分中包含对Core Data的总结。更多Core Data的信息,请阅读Core Data编程指南。 NSPersistentDocument Core Data教程和底层Core Data教程则一步一步地教您创建基于文档和非基于文档的Core Data应用程序的基本流程。
这种模式定义一种对象间一对多的依赖关系,使得当一个对象的状态发生变化时,其它具有依赖关系的对象可以自动地被通知和更新。观察者模式本质上是个发布-定阅模型,主体和观察者具有宽松的耦合关系。观察和被观察对象之间可以进行通讯,而不需要太多地了解对方。
Cocoa的通告机制实现了一对多的消息广播,其实现方式符合观察者模式。在这种机制中,程序里的对象将自己或其它对象添加到一或多个通告的观察者列表中,每个通告由一个全局的字符串(即通告的名称)标识。希望向其它对象发送通知的对象-也就是被观察的对象-负责创建一个通告对象,并将它发送到通告中心。通告中心则负责确定通告的观察者,并通过消息将通告发送给观察者对象。通告消息激活的方法必须遵循特定的单参数签名格式,方法的参数就是通告对象,包含通告的名称、被观察的对象、以及一个含有补充信息的字典。
通告的发送是一个同步的过程。在通告中心将通告广播给所有的观察者之前,发送对象不会再得到程序的控制权。对于异步的处理方式,控制权会在您将通告放入通告队列中之后立即返回到发送对象,当通告到达队列的顶部时,通告中心会将它广播出去。
常规的通告-也就是那些由通告中心广播的通告-只能在进程内部传播。如果您希望将通告广播给其它进程,需要使用分布式通告中心及其相关的API。
使用通告可以有很多原因。例如,借助通告机制,您可以根据程序中其它地方发生的事件改变用户界面元素显示信息的方式。或者,您可以用通告来保证文档中的对象在文档窗口关闭之前保存自己的状态。通告的一般目的是将事件通知给其它程序对象,使它们可以做出恰当的反应。
但是,通告的接收对象只能在事件发生之后进行反应,这和委托机制有显著的不同。被委托的对象有机会拒绝或修改委托对象希望进行的操作。另一方面,观察者对象不能直接影响一个即将发生的操作。
与通告有关的类有NSNotification
(通告对象)、NSNotificationCenter
(用于发送通告和添加观察者)、NSNotificationQueue
(负责管理通告队列)、和NSDistributedNotificationCenter
。很多Cocoa框架都发布和发送通告,其它对象都可以成为这些通告的观察者。
键-值观察是使对象可以在其它对象的具体属性发生变化时得到通知的一种机制。它基于名为NSKeyValueObserving
的非正式协议。被观察的属性可以是简单的属性、一对一的关系、或者一对多的关系。键-值观察在模型-视图-控制器模式中特别重要,因为它使视图对象-通过控制器层-可以观察到模型对象的变化,因此是Cocoa绑定技术的必要组件(参见"控制器类"部分)。
Cocoa为很多NSKeyValueObserving
方法提供了缺省的“自动”实现,使所有遵循该协议的对象都具有属性-观察的能力。
键-值观察和通告机制类似,但在一些重要的方面也有不同。在键-值观察中,没有为所有观察者提供变化通告的中心对象,而是将变化的通告直接传递给观察对象。还有,键-值观察直接和具体的对象属性相关联。而通告机制则更广泛地关注程序的事件。
参与键-值观察(KVO)的对象必须满足特定的要求-或者说是遵循KVO,记忆起来更为方便。对于自动观察来说,这需要满足键-值编码的要求(也称为遵循KVC),并使用遵循KVC的方法(也就是存取方法)。键-值编码是一个与自动获取和设置对象属性值有关的机制(基于相关的非正式协议)。
您可以禁用自动的观察者通告,并通过NSKeyValueObserving
非正式协议及相关范畴中的方法实现手工通告,从而对KVO通告进行精化。
这种模式为某些对象定义接口,使其充当其它对象的代理或占位对象,目的是进行访问控制。这种模式可以用于为一个可能是远程的、创建起来开销很大的、或者需要保证安全的对象创建代表对象,并在代表对象中为其提供访问控制的场合。它在结构上和装饰模式类似,但服务于不同的目的;装饰对象的目的是为另一个对象添加行为,而代理对象则是进行访问控制。
NSProxy
类定义的接口适用于为其它的、甚至是尚未存在的对象充当代理或占位符的对象。代理对象通常将消息转发给自己代表的对象,但是也可以通过装载其代表的对象或对自身进行改造来对消息进行响应。虽然NSProxy
是一个抽象类,但它实现了NSObject
协议及其它根对象应该具有的基本方法,它实际上是和NSObject类一样,是一个类层次的根类。
NSProxy
的具体子类可以完成代理模式规定的目标,比如对开销大的对象进行迟缓实例化,或者实现安全守卫对象等。NSDistantObject
就是Foundation框架中的NSProxy
类的一个具体子类,负责为透明的分布式消息传递实现远程的代理对象。 NSDistantObject
对象是分布式对象架构的一部分。这些对象充当其它进程或线程中的对象的代理,帮助实现那些线程或进程中的对象之间的通讯。
NSInvocation
对象采纳了命令模式,也是分布式对象架构的一部分。
Cocoa只在分布式对象中使用NSProxy
对象。NSProxy
对象是NSDistantObject
和NSProtocolChecker
两个具体子类的特殊实例。分布式对象不仅可以用于进程间(在同一台或不同的计算机上都可以)的消息传递,还可以用于实现分布式计算或并行处理。如果您希望将代理对象用于其它目的,比如昂贵资源的创建或者安全,则需要实现您自己的NSProxy
具体子类。
这种模式确保一个类只有一个实例,并提供一个全局的指针作为访问通道。该类需要跟踪单一的实例,并确保没有其它实例被创建。单件类适合于需要通过单个对象访问全局资源的场合。
有几个Cocoa框架类采用单件模式,包括NSFileManager
、NSWorkspace
、和NSApplication
类。这些类在一个进程中只能有一个实例。当客户代码向该类请求一个实例时,得到的是一个共享的实例,该实例在首次请求的时候被创建。
使用由单件类返回的共享实例和和使用非单件类的实例没有什么不同,只是您不能对该实例进行拷贝、保持、和释放(相关的方法被重新实现为空操作)。您在条件许可的情况下也可以创建自己的单件类。
这种模式为某个操作中的算法定义框架,并将算法中的某些步骤推迟到子类实现。模板方法模式使子类可以重定义一个算法中的特定步骤,而不需要改变算法的结构。
模板方法模式是Cocoa的基本设计,事实上也是一般的面向对象框架的基本设计。Cocoa中的模式使一个程序的定制组件可以将自己挂到算法上,但何时和如何使用这些定制组件,由框架组件来决定。Cocoa类的编程接口通常包括一些需要被子类重载的方法。在运行环境中,框架会在自己所执行的任务过程中的某些点调用这些所谓的一般方法。一般方法为定制代码提供一个结构,目的是为当前正在执行且由框架类负责协调的任务加入具体程序的的行为和数据。
为了使用Cocoa采纳的模板方法模式,您必须创建一个子类,并重载有关的方法。框架会调用这些方法,并将具体应用程序的信息输入到正在执行的算法中。如果您正在编写自己的框架,则可能应该将这个模式包含到您的设计中。
Cocoa的文档架构就是一个特别的(也是重要的)、采纳模板方法模式进行框架方法重载的设计实例。几乎所有创建和管理多个文档、而且每个文档有各自显示窗口的Cocoa应用程序都基于文档架构。在这个架构中,有三个互相协作的框架类:NSDocument
、NSWindowController
、和NSDocumentController
。NSDocument
对象管理模型对象,代表文档的数据,它会根据用户的请求将数据写到文件中,或者重新装载数据并用这些数据重建模型对象;NSWindowController
对象管理负责管理特定文档的用户界面;基于文档的应用程序的NSDocumentController
对象管理负责跟踪和管理所有打开的文档,或者协调应用程序的活动。在运行时,这些对象从Application Kit接收要求执行具体操作的消息。应用程序开发者必须重载很多由这些消息激活的方法,以便添加应用程序的具体行为。
Cocoa文档架构的设计在很大程度上也受到模型-视图-控制器模式的影响。
您可以在Xcode的新建工程(New Project)助手中选择基于文档的Cocoa应用程序(Cocoa Document-based Application)模板,创建基于文档的Cocoa应用程序工程。然后,您需要实现一个NSDocument
的定制子类,选择实现 NSWindowController
和NSDocumentController
的定制子类。Application Kit为您提供很多应用程序需要的文档管理逻辑。