《Cocoa设计模式》读书笔记(2)

来源:互联网 发布:hyde知乎 编辑:程序博客网 时间:2024/06/08 07:17

新浪微博:东门兜

第13章 单例(+sharedInstance)

当应用程序中必须恰好只有类的一个实例并且它必须可以被其他对象轻松地访问时,可以使用单例模式。
面向对象程序设计的重要特性之一是减少对这类全局变量的需要。
为了实现这种模式,类对象提供了可以全局访问并且可用于获得共享对象的实例的方法。与此同时,禁止使用+alloc方法,以阻止创建额外的实例。在第一次要求时将创建单个共享实例,自此以后每次都会返回同一个实例。
在设计对象时不过度应用这个模式是很重要的。仅当类表示确实只会存在一次的事物时,才使之成为单例。有时,事物起初似乎符合这个条件,但是随着应用程序的演化而不再符合。例如,如果本章中用作示例的假想游戏拓展到具有多个游戏玩家变体,那么每个变体都将需要它自己的高分表。在这种情况下,用于高分表的单例方法将不再适用。


第14章 通知(NSNotification)

可以使用提供全局变量的框架来避免在你的代码中硬编码常量字符串。
发布通知给NSNotificationCenter是同步的。同步行为也意味着在你实现方法对通知做出反应时,应该留意后果。如果在通知处理代码中执行过长的操作,就会延迟其他对象对通知的接收,以及延迟返回到发布通知的代码。
当你需要比仅仅延迟消息更复杂的异步行为时,可以使用Cocoa的NSNotificationQueue。NSNotificationQueue实例实现了一个异步先进先出(First In First Out,FIFO)队列。
Cocoa提供了一种用于发布通知的机制,可以把它们传输给同一台计算机上运行的所有应用程序。可以使用Cocoa的NSDistributedNotification。
通知模式的最大的弱点是:类的设计者必须预先考虑对通知的要求。
事实上,在许多使用通知的相同情况下,Cocoa类也使用委托模式。一般规则是,当可能有许多对象观察通知时,就使用通知模式;当恰好只给一个对象提供机会来影响所发生的变化或者对其做出反应时,就使用委托模式。


第15章 委托(delegate)

自定义标准的NSWindow类的行为的一种方式是子类化它,并且在子类中实现新的行为。不过,子类化需要子类与其超类之间具有非常紧密的耦合。过度使用子类化将导致创建许多特定于应用程序并且可重用性非常差的类。
委托消除了可能需要多重继承的最常见的情形之一。如果设计目标是在关闭最后一个显示来自连接的内容的窗口时关闭网络连接,就可能诱使人们尝试并创建一个新类,同时继承管理网络连接的能力和管理窗口关闭的能力。不过,Objective-C语言不支持多重继承,并且与尝试合并两种单独的继承层次结构相比,委托模式提供了更灵活、更简单的解决方案。
许多Cocoa类都支持把委托用作子类化的替代选择。

委托消息通常包括以下3个动词之一:should、will或did。
使用should的消息应该返回一个值,并且通常接受一个参数,它直接标识发送消息的对象。在对发送消息的对象做出改变以前把这些消息发送给委托,并且给委托提供一个机会来影响所做的改变。
使用will的消息不应该返回值。
最后,在改变发生后发送did消息。

使用数据源可以使模型-视图-控制器模式中的子系统保持分隔开。
使用数据源具有支持高效数据处理和内存使用的额外优点。
数据源对象不会被使用它的对象保留。

类的设计者必须预见对委托或数据源的需要。如果没有提供委托支持或者没有在适当的情形下发送委托消息,那么在实现应用程序特有的行为时可能只能选择子类化。


第16章 层次结构(NSView、XML、NSMutableArray)

每个NSView对象都有它自己的坐标系统,通过视图对象进行的绘图把视图的左下角视为原点。因此,每个视图实际上会记录两个矩形。bounds矩形是在视图的坐标系统中表示的,frame矩形也是如此。它们之间的区别在于:bounds是在视图的坐标系统中表示的,而frame则位于超视图的坐标系统中。这使得有可能在不同视图的坐标系统之间转换点。
由于bounds矩形和frame矩形对于它们的大小和原点可以具有不同的值,坐标系统不一定是简单的转换,还可能涉及缩放比例。由于从一种坐标空间到另一种坐标空间转换点的固有复杂性,Cocoa提供了多个NSView方法来提供帮助。
像这样具有多种坐标系统使得可以相对地重新定位视图组以及调整它们的大小。可以在窗口中四处移动视图的整个层次结构,或者从一个窗口中移到另一个窗口中,并且只需改变层次结构中最上面的视图的框架。这极大地简化了多个视图对象的操纵。假如在编写绘图代码时无须考虑转换和缩放比例,那么使每个视图都具有单独的坐标系统也使得更容易编写用于特定视图的绘图代码。

由于Cocoa使用视图的层次结构表示用户界面,就有可能减少子类化的需要。开发人员被鼓励添加视图,作为互相之间的子视图,从更简单的构件构造复杂的用户界面。由于每个视图都有它自己的坐标系统,可以组合复杂的视图对象组,然后轻松地从一个窗口移到另一个窗口,或者单个窗口中进行换入和换出。


第17章 插座变量、目标和动作(IBOutlet、IBAction)

在通过模型-视图-控制器模式提供用户界面的所有Cocoa应用程序中,都使用了插座变量、目标和动作设计模式。目标通常指向视图或控制器子系统中的对象。模型子系统中的对象不应该具有插座变量,因为插座变量打算指向视图或控制器对象,并且模型应该没有对视图或控制器对象的任何依赖。模型对象应该没有动作方法,这是由于动作是由视图子系统对象发送的,并且模型对象应该不会与视图子系统直接交互。
像Interface Builder这样的工具以图形方式设置插座变量和动作的能力减少了实现用户界面并把它们与应用程序代码相集成所需的代码量。
Cocoa用户界面类很少被子类化。


第18章 响应者链(-sendAction:to:from:)

责任链模式旨在解除消息的发送者与接收者之间的耦合。消息或事件沿着对象的链表传递,直到其中一个对象处理消息为止,从而给多个对象提供了机会来处理或忽略消息。由于Objective-C的动态性,这个模式的Cocoa实现的功能极其强大,并且在大多数情况下比它在其他应用程序框架中的对应技术更简单。

为了沿着拓展的响应者链发送消息,NSApplication实现了-sendAction:to:from:方法。在沿着发送消息时,为了查明哪个对象将作出响应,无须实际地发送消息,可代之以使用NSApplication方法-targetForAction:to:from:。


第19章 联合存储(类别中增加实例变量、NSDictionary)

与每个NSObject实例关联的字典的使用是完全无限制的。不过,访问存储在字典或映射表中的值并不像直接访问实例变量那样高效。通常可以利用单个机器指令从程序代码中访问实例变量,但是联合存储需要多个方法或函数调用,计算散列值,以及把内存访问索引进表中。存储关联的值需要用于键和值的内存。如果每个对象都使用关联的存储,存储所有键和值所需的内存将超过在实例变量中存储值所需的内存。如果很少需要存储值,使用联合存储就有非常大的好处。无须在每个实例中存储未使用的实例变量,仅当在实际使用值时才预留内存。
当可以选择进行子类化时,简单地在子类中添加实例变量可能是最佳的选择。使用联合存储的最灵活的技术之一是提供一个NSDictionary对象作为实例变量。
对于需要许多参数的复杂方法,利用单个NSDctionary参数实现可能更好一些。通过使用联合存储传递参数,可以避免方法的扩散。联合存储也有助于把发送者与将来的变化隔离开。如果以后的方法版本允许添加新参数,也无须改变消息。可以简单地向字典中添加更多的键/值对。如果删除参数,字典中任何额外的键都将被忽略。这种方法的缺点是发送者需要额外的代码来配置和填充NSDctionary实例。


第20章 调用(NSInvocation、NSTimer、-performSelector)

每个方法都具有两个部分:选择器和方法签名,这两个部分是正确配置NSInvocation对象所需的。选择器是不带任何类型信息的消息的名称,即选择器SEL;要构建完整的消息,还需要知道每个参数的类型和返回值类型。这种类型信息称为方法签名,即NSMethodSignature。
要使用NSInvocation发送消息,可以创建一个实例,然后配置它。一旦配置了实例,就可以在合适的时间调用它,从而导致发送Objective-C消息。然后可以从NSInvocation对象中获取消息的返回值。NSInvocation实例可以调用多次,可能会更改目标、参数,甚至在调用之前更改消息选择器。
在创建计时器可以不指定目标、选择器和用户信息对象,代之以使用+scheduledTimerWithInterval:invocation:repeats:方法简单地传递NSInvocation对象。
延迟的消息的一种有趣的应用是使用值为0的时间间隔。在这种情况下,仍然会延迟消息,但是只会延迟到下一个经过运行循环为止。当应该在当前事件的处理完成之后发送消息时这可能是有用的。
调用打包了一个Objective-C方法,使得可以像对象一样处理它。使用调用,开发人员可以自由地创建和修改Objective-C消息。可以存储消息并在以后某个时间发送它,或者定期重复发送。消息也可以被复制,并且可以被捕获和转发给其他对象,即使对象在其他应用程序中也是如此。


第21章 原型(NSMatrix、NSCell和Copy)

原型是复制用于实现应用程序特性的对象。确切地讲,复制现有的对象通常比从头开始分配和初始化新实例提供了更大的灵活性。原型模式避免了硬编码对象之间的关系的需要。如果提供一个原型按钮,就会得到按钮的网格。如果提供一个原型文本框,就会得到文本框的网格。
Interface Builder库中的对象都是原型。当从库中把对象拖到应用程序窗口中时,就会复制对象,并且会包含它的当前配置和状态。


第22章 享元(NSNumber、NSCell)

享元模式最小化了使用对象所需的内存量和处理器开销。面向对象程序设计的优点有时不足以弥补使用对象的开销,尤其是在同时需要大量的对象实例时。享元模式允许实例共享,从而减少了实例的数量,同时保留了使用对象的优点。实现享元模式的类称为“享元”。


第23章 装饰器(特殊的复合)

Cocoa的许多标准用户界面面板允许添加你自己的装饰器。例如,NSSavePanel类、NSFontPanel类……都提供了一个-(void)setAccessoryView:(NSView *)aView方法,用于添加你希望作为“附属视图”显示在相关面板上的任何视图。附属视图装饰面板,并且由于附属视图可以是你提供的任何视图,你就能够添加按钮或者应用程序所需的其他任何用户界面元素。面板可以自动调整大小以适应附属视图。可以使用附属视图给标准面板添加一些能力,而不必子类化多个不同的面板类。
面向对象继承关系的功能很强大,但是它们也是在设计导致耦合的主要原因。继承关系是在编译时静态设置的,并且会影响子类的所有实例。使用“has-a”关系的复合通常提供了一种灵活的方法,用于替代子类化。通过复合拓展对象是在运行时动态进行的,并且可以应用于每个实例。有可能通过复合给相同的对象添加多种能力,而不会导致所需的类的数量猛增。

0 0