现在的位置: 首页 > 综合 > 正文

Cocoa如何应用设计模式

2011年09月10日 ⁄ 综合 ⁄ 共 8269字 ⁄ 字号 评论关闭

在Cocoa中到处都可以找到设计模式的应用,基于模式的机制或架构在Cocoa框架和Objective-C运行环境及语言中是很常见的。Cocoa经常把自己与众不同的工作机制建立在模式上,它的设计受到诸如语言能力或现有架构这样因素的影响。

本部分包含设计模式:可重用的面向对象软件的元素一书中编目的大多数设计模式的介绍。每个设计模式都有一个总结性的描述,以及该模式的Cocoa实现的讨论。文中列出的都是Cocoa实现的模式,每个模式的讨论都发生在特定的Cocoa环境中。我们推荐您熟悉这些模式,您会发现这些模式在Cocoa软件开发中非常有用。

Cocoa中设计模式的实现有不同的形式。下面部分中描述的一些设计—比如协议和范畴—是Objective-C语言的特性;在另外一些场合中,“模式的实例”被实现为一个类或一组相关的类(比如类簇和单件类);还有一些场合下,模式表现为一个大的框架结构,比如响应者链模式。对于某些基于模式的机制,您几乎可以“免费”使用;而另外一些机制则要求您做一些工作。即使对于Cocoa没有实现的模式,我们也鼓励您在条件许可的情况下自行实现,比如在扩展类的行为时,对象合成(装饰模式)技术通常就比生成子类更好。

有两个设计模式没有在下面的内容中进行讨论,即模型-视图-控制器(MVC)模式和对象建模。MVC是一种复合或聚合模式,就是说它基于几种不同类型的模式。对象建模在四人组的分类目录中没有对应类别,它源自关系数据库领域。然而,MVC和对象建模在Cocoa中可能是最重要和最普遍的设计模式或用语,而且它们在很大程度上是相关的。它们在几个技术的设计中发挥关键的作用,包括绑定、撤消管理、脚本控制、和文档架构。要了解更多有关这些模式的信息,请参见"模型-视图-控制器设计模式""对象建模"部分。

本部分包含如下主要内容:

抽象工厂模式
适配器模式
责任链模式
命令模式
合成模式
装饰模式
表观模式
跌代器模式
仲裁者模式
备忘录模式
观察者模式
代理模式
单件模式
模板方法模式


抽象工厂模式

提供一个接口,用于创建与某些对象相关或依赖于某些对象的类家族,而又不需要指定它们的具体类。通过这种模式可以去除客户代码和来自工厂的具体对象细节之间的耦合关系。

类簇

类簇是一种把一个公共的抽象超类下的一些私有的具体子类组合在一起的架构。抽象超类负责声明创建私有子类实例的方法,会根据被调用方法的不同分配恰当的具体子类,每个返回的对象都可能属于不同的私有子类。

Cocoa将类簇限制在数据存储可能因环境而变的对象生成上。Foundation框架为NSStringNSDataNSDictionaryNSSet、和NSArray对象定义了类簇。公共超类包括上述的不可变类和与其相互补充的可变类NSMutableStringNSMutableDataNSMutableDictionaryNSMutableSet、和NSMutableArray

使用和限制

当您希望创建类簇代表的类型的可变或不可变对象时,可以使用类簇中的某个公共类来实现。使用类簇是在简洁性和扩展性之间进行折衷。类簇可以简化类接口,因此使其更易于学习和使用,但是创建类簇抽象类的定制子类则会变得更加困难。

进一步阅读: "类簇" 部分提供有关Cocoa类簇的更多信息。

 

适配器模式

将一个类接口转化为客户代码需要的另一个接口。适配器使原本由于兼容性而不能协同工作的类可以工作在一起,消除了客户代码和目标对象的类之间的耦合性。

协议

协议是一个编程语言级别(Objective-C)的特性,它使定义适配器模式的实例成为可能(在 Java中的“接口”和“协议”是同义的)。如果您希望一个客户对象和另一个对象进行交流,但由于它们的接口不兼容而导致困难,您就可以定义一个协议,它本质上是一系列和类不相关联的方法声明。这样,其它对象的类就可以正式采纳该协议,并通过实现协议中的全部方法来“遵循”该协议。结果,客户对象就可以通过协议接口向其它对象发送消息。

协议是一组独立于类层次的方法声明,这样就有可能象类的继承那样,根据对象遵循的协议对其进行分组。您可以通过NSObjectconformsToProtocol:方法来确认一个对象的协议关系。

除了正式协议之外,Cocoa还有一个非正式协议的概念。这种类型的协议是NSObject类中的一个范畴(category),这样就使所有的对象都成为范畴方法的潜在实现者(参见"范畴"部分)。非正式协议的方法可以选择性地实现。非正式协议是委托机制实现的一部分(参见"委托"部分。

请注意,协议的设计和适配器模式并不完全匹配。但它是使接口不兼容的类在得以协同工作的手段。

使用和限制

协议主要用于声明层次结构上不相关的类为了互相通讯而需要遵循的接口。但是,您也可以将协议用于声明对象的接口,而隐藏相应的类。Cocoa框架包括很多正式协议,这些协议使定制子类可以和框架进行特定目的的通讯。举例来说,Foundation框架中包含NSObjectNSCopying、和NSCoding协议,都非常重要。Application Kit中的协议包括NSDraggingInfoNSTextInput、和NSChangeSpelling

正式协议要求遵循者实现协议声明的所有方法。它们也是零碎的,一旦您定义一个协议并将它提供给其它类,将来对协议的修改会使那些类不能工作。

进一步阅读:有关正式协议的更多信息请参见Objective-C编程语言文档中“扩展类”部分的讨论。

 

责任链模式

通过为多个对象提供处理请求的机会,避免请求的发送者和接收者产生耦合。将接收对象串成链,并将请求沿着接收者链进行传递,直到某个对象对其进行处理。对象或者处理该请求,或者将它传递给链中的下一个对象。

响应者链

Application Kit框架中包含一个称为响应者链的架构。该链由一系列响应者对象(就是从NSResponder继承下来的对象)组成,事件(比如鼠标点击)或者动作消息沿着链进行传递并(通常情况下)最终被处理。如果给定的响应者对象不处理特定的消息,就将消息传递给链中的下一个响应者。响应者在链中的顺序通常由视图的层次结构来决定,从层次较低的响应者对象向层次较高的对象传递,顶点是管理视图层次结构的窗口对象,窗口对象的委托对象,或者全局的应用程序对象。事件和动作消息在响应者链中的确切传递路径是不尽相同的。一个应用程序拥有的响应者链可能和它拥有的窗口(甚至是局部层次结构中的视图对象)一样多,但每次只能有一个响应者链是活动的—也就是与当前活动窗口相关联的那个响应链。

还有一个与响应者链相类似的链,用于应用程序的错误处理。

视图层次的设计应用的是合成模式(参见"合成模式"部分),它和响应者链密切相关。动作消息—源自控件对象的消息—基于目标-动作机制,是命令模式(参见"命令模式"部分)的一个实例。

使用和限制

当您通过Interface Builder或以编程的方式为程序构造用户界面时,可以“免费”得到一个或多个响应者链。响应者链和视图层次结构一起出现,当您使一个视图对象成为窗口内容视图的子视图时,视图层次结构就自动生成了。如果您将一个定制视图加入到一个视图层次结构中,它就变成响应者链的一部分;如果您实现了恰当的NSResponder方法,就可以接收和处理事件及动作消息。定制对象是窗口对象或全局应用程序对象NSApp的委托对象,也可以接收和处理那些消息。

您也可以用编程的方式将定制的响应者对象注入到响应者链中,以及通过编程操作响应者在链中的顺序。

进一步阅读: 处理事件和动作消息及程序错误的的响应者链在Cocoa事件处理指南Cocoa的错误处理编程指南文档中进行描述。本文档在"合成模式"部分对视图层次结构有总结性的介绍,在"核心应用程序架构"部分有更全面的描述。

 

命令模式

这种模式将请求封装为对象,使您可以用不同的请求来对客户代码进行参数化;对请求进行排队和记录,并支持可撤消(undoable)的操作。请求对象将一或多个动作绑定在特定的接收者上。命令模式将发出请求的对象和接收及执行请求的对象区分开来。

调用对象

NSInvocation类的实例用于封装Objective-C消息。一个调用对象中含有一个目标对象、一个方法选择器、以及方法参数。您可以动态地改变调用对象中消息的目标及其参数,一旦消息被执行,您就可以从该对象得到返回值。通过一个调用对象可以多次调用目标或参数不同的消息。

创建NSInvocation对象需要使用NSMethodSignature对象,该对象负责封装与方法参数和返回值有关系的信息。NSMethodSignature对象的创建又需要用到一个方法选择器。NSInvocation的实现还用到Objective-C运行环境的一些函数。

使用和限制

NSInvocation对象是分布式、撤消管理、消息传递、和定时器对象编程接口的一部分。在需要去除消息发送对象和接收对象之间的耦合关系的类似场合下,您也可以使用。

分布式对象是一种进程间通讯技术,关于这个主题的更多信息请参见"代理模式"部分。

进一步阅读:调用对象的细节请阅读NSInvocation的类参考文档。您也可以从下面的文档中获取相关技术的信息:Objective-C编程语言文档中的分布式对象撤消架构定时器以及之后的部分。

 

目标-动作

目标-动作机制使控件对象—也就是象按键或文本输入框这样的对象—可以将消息发送给另一个可以对消息进行解释并将它处理为具体应用程序指令的对象。接收对象,或者说是目标,通常是一个定制的控制器对象。消息—也被称为动作消息—由一个选择器来确定,选择器是一个方法的唯一运行时标识。典型情况下,控件拥有的单元对象会对目标和动作进行封装,以便在用户点击或激活控件时发送消息(菜单项也封装了目标和动作,以便在用户选择时发送动作消息)。目标-动作机制之所以能够基于选择器(而不是方法签名),是因为Cocoa规定动作方法的签名和选择器名称总是一样的。

使用和限制

当您用Interface Builder构建程序的用户界面时,可以对控件的动作和目标进行设置。您因此可以让控件具有定制的行为,而又不必为控件本身书写任何的代码。动作选择器和目标连接被归档在nib文件中,并在nib文件被解档时复活。您也可以通过向控件或它的单元对象发送setTarget:setAction:消息来动态地改变目标和动作。

目标-动作机制经常用于通知定制控制器对象将数据从用户界面传递给模型对象,或者将模型对象的数据显示出来。Cocoa绑定技术则可以避免这种用法,有关这种技术的更多信息请参见Cocoa绑定编程主题文档。

Application Kit中的控件和单元并不保持它们的目标。相反,它们维护一个对目标的弱引用。进一步的信息请参见"委托、观察者、和目标的所有权" 部分。

进一步阅读:更多信息请参见"目标-动作机制"部分。

 

合成模式

这种模式将互相关联的对象合成为树结构,以表现部分-全部的层次结构。它使客户代码可以统一地处理单独的对象和多个对象的合成结果。

合成对象是模型-视图-控制器聚集模式的一部分。

视图层次结构

一个窗口包含的视图对象(NSView对象)在内部构成了一个视图层次结构。层次结构的根是窗口对象(NSWindow对象)和它的内容视图。内容视图就是填充到窗口内容方框中的透明视图,添加到内容视图中的视图都是它的子视图,而这些子视图又会成为下一级视图的父视图。一个视图有一个(且只有一个)父视图,可以有零或多个子视图。在视觉上,您可以将这个结构理解为包含关系:父视图包含它的子视图。图4-2显示视图层次的结构以及在视觉上的关系。

 

图4-2  视图层次的结构及其在视觉上的关系

The view hierarchy, structural and visual

 

视图层次是一个结构方面的架构,在图形描画和事件处理上都扮演一定的角色。一个视图有两个影响图形操作位置的边界框,即边框(frame)和边界(bound)。边框是外部边界,表示视图在其父视图坐标系统中的位置,它负责定义视图的尺寸,并根据视图边界对图形进行裁减。边界则是内部的边界框,负责定义视图对象自身描画表面的内部坐标系统。

当窗口系统要求一个窗口做好显示准备时,父视图会在其子视图之前被要求进行渲染。当您向一个视图发送消息时—比如发送一个重画视图的消息—该消息就会被传播到子视图。因此,您可以将视图层次结构中的一个分支当成一个统一的视图来处理。

响应者链也把视图层次用于处理事件和动作消息。请参见责任链模式部分中("责任链模式")关于响应者链的总结描述。

使用和限制

无论以编程的方式,还是通过Interface Builder,当您将一个视图加入到另一个视图中时,都需要创建或修改视图层次结构。Application Kit框架自动处理与视图层次结构相关联的所有关系。

进一步阅读:如果需要了解更多视图层次结构的信息,请阅读本文档的"视图层次结构"部分。Cocoa描画指南文档中也对视图层次结构进行讨论。

 

装饰模式

这种模式动态地将额外的责任附加到一个对象上。在进行功能扩展时,装饰是子类化之外的一种灵活的备选方法。和子类化一样,采纳装饰模式可以加入新的行为,而又不必修改已有的代码。装饰将需要扩展的类的对象进行包装,实现与该对象相同的接口,并在将任务传递给被包装对象之前或之后加入自己的行为。装饰模式表达了这样的设计原则:类应该接纳扩展,但避免修改。

一般性的说明

装饰是用于对象合成的模式。在您自己的代码中应该鼓励使用对象的合成(参见"什么时候需要生成子类"部分)。然而,Cocoa自己提供了一些基于这种模式的类和机制(在下面的部分进行讨论)。在这些实现中,扩展对象并不完全复制它所包装的对象的接口,虽然具体实现中可以使用不同的技术来进行接口共享。

Cocoa在实现某些类时用到了装饰模式,包括NSAttributedStringNSScrollView、和NSTableView。后面两个类是复合视图的例子,它们将其它一些视图类的对象组合在一起,然后协调它们之间的交互。

委托

委托是在宿主对象中嵌入一个指向另一对象(也就是委托对象)的弱引用(一个未保持的插座变量),并不时地向该委托对象发送消息,使其对有关的任务进行输入的机制。宿主对象一般是一个“复活”的框架对象(比如一个NSWindowNSXMLParser对象),它寻求完成某项工作,但又只能以一般的方式来进行。委托几乎总是一个定制类的实例,它负责配合宿主对象,在有关任务的特定点(参见图4-3)上提供与具体程序有关的行为。这样,委托机制使我们可以对另一个对象的行为进行修改或者扩展,而不需要生成子类。

 

图4-3  框架对象向它的委托对象发送消息

Framework object sending a message to its delegate

 

简而言之,委托就是一个对象将任务委托给另一个对象,它是面向对象编程的常用技术。然而,Cocoa以独特的方式实现委托机制。宿主类用非正式协议—即NSObject中的范畴—来定义委托对象可能选择实现的接口。委托对象不必象采纳正式协议那样实现所有的协议方法。在向委托对象发送消息之前,宿主对象可以首先确定相应的方法是否实现(通过respondsToSelector:消息),以避免运行时例外。有关正式协议和非正式协议的更多信息,请参见"协议"部分。

Cocoa框架中的一些类也向它们的数据源发送消息。数据源在各个方面都和委托一样,除了它的目的是为宿主对象提供数据,以传递给浏览器、表视图、或者类似的用户界面视图。和委托不同的是,数据源还必须实现某些协议方法。

委托不是装饰模式的严格实现。宿主(委托)对象并没有包装它希望扩展的类的实例。相反,委托是对委托框架类的行为进行特殊化。除了框架类声明的委托方法之外,它们也没有公共的接口。

Cocoa中的委托也是模板方法模式("模板方法模式")的一部分。

使用和限制

委托是Cocoa框架中的一种常用的设计。Application Kit框架中的很多类都向它们的委托发送消息,包括NSApplicationNSWindow、和NSView的几个子类。Foundation框架中的一些类,比如NSXMLParserNSStream,也维护自己的委托。您应该总是使用类的委托机制,而不是生成类的子类,除非委托方法不能完成您的目标。

虽然您可以动态地改变委托,但是同时只能有一个对象可以成为委托。因此,如果您希望当特定的程序事件发生时,有多个对象可以同时得到通知,则不能使用委托。在这种情况下您可以使用通告机制。如果委托对象实现了一或多个框架类声明的通告方法,就会自动接收到其委托框架对象的通告。请参考观察者模式( "观察者模式")中有关通告的讨论。

Application Kit框架中的向外委托任务的对象并不保持它们的委托或数据源,而是维护一个弱引用,更多信息请参见"委托、观察者、和目标的所有权"部分。

进一步阅读:关于委托的进一步信息请参见"委托和数据源"部分。

 

范畴

范畴是Objective-C语言的一个特性,用于为一个类增加方法(接口和实现),而不必生成子类。类原始声明的方法和通过范畴添加的方法在运行时没有区别—在您的程序的作用范围内。范畴中的方法成为类类型的一部分,并被所有的子类继承。

和委托一样,范畴并没有严格适配装饰模式。它实现了该模式的目的,但采用不同的实现方式。范畴加入的行为是在编译时生成的,而不是动态得到的。而且,范畴并没有封装被扩展的类的实例。

使用和限制

Cocoa框架中定义了很多范畴,大多数都是非正式协议(在"协议"部分中进行总结)。它们通常使用范畴来对相关的方法进行分组。您也可以在代码中实现范畴,以在不生成子类的情况下对类进行扩展,或者对相关的方法进行分组。但是您需要注意如下两点:

  • 您不能为类添加实例变量。

  • 如果您对现有的方法进行重载,则应用程序可能产生预料之外的行为。

进一步阅读: 更多有关范畴的信息请参见Objective-C编程语言一文中的“类的扩展”部分。

 

表观模式

这种模式为子系统中的一组接口提供统一的接口。表观模式定义一个更高级别的接口,通过减少复杂度和隐藏子系统之间的通讯和依赖性,使子系统更加易于使用。

NSImage

NSImage类为装载和使用基于位图(比如JPEG、PNG、或者TIFF格式)或向量(EPS或PDF格式)的图像提供统一的接口。NSImage可以为同一个图像保持多个表示,不同的表示对应于不同类型的NSImageRep对象。NSImage可以自动选择适合于特定数据类型和显示设备的表示。同时,它隐藏了图像操作和选择的细节,使客户代码可以交替使用很多不同的表示。

使用和限制

由于NSImage支持几种不同的图像表示,因此某些属性可能不能适用。举例来说,您可能不能取得图像中一个像素的颜色,如果潜在的图像表示使基于向量且与设备无关的话。

请注意:NSImage和图像表示的讨论请参见Cocoa描画指南

 

迭代器模式

这种模式提供一种顺序访问聚合对象(也就是一个集合)中的元素,而又不必暴露潜在表示的方法。迭代器模式将访问和遍历集合元素的责任从集合对象转移到迭代器对象。迭代器定义一个访问集合元素的接口,并对当前元素进行跟踪。不同的迭代器可以执行不同的遍历策略。

NSEnumerator

Foundation框架中的NSEnumerator类实现了迭代器模式。NSEnumerator抽象类的私有具体子类返回的枚举器对象可以顺序遍历不同类型的集合—数组、集合、字典(值和键)—并将集合中的对象返回给客户代码。

NSDirectoryEnumerator是一个不紧密相关的类,它的实例可以递归地枚举文件系统中目录的内容。

使用和限制

NSArrayNSSet、和NSDictionary这样的集合类都包含相应的方法,可以返回与集合的类型相适用的枚举器。所有的枚举器的工作方式都一样。您可以在循环中向枚举器发送nextObject消息,如果该消息返回nil,而不是集合中的下一个对象,则退出循环。

仲裁者模式

这种模式定义的对象用于封装一组对象的交互机制。仲裁者模式可以避免对象之间显式的互相引用,使对象之间的耦合变得宽松,也使您可以独立地改变它们的交互方式。这些对象也因此可以更具重用性。

仲裁者对象集中了系统中的对象之间的复杂通讯和控制逻辑。这些对象在状态发生改变时会告诉仲裁者对象,反过来,也对仲裁者对象的请求进

抱歉!评论已关闭.