在软件设计领域,设计模式是对通用问题的可复用的解决方案。设计模式是一系列帮你写出更可理解和复用代码的模板,设计模式帮你创建松耦合的代码以便你不需要费多大力就可以改变或者替换代码中的组件。
创建型:单利(单态)和 抽象工厂
结构型:模型-视图-控制器、装饰器、适配器、外观(门面)和组合模式
行为型:观察者、备忘录、责任链和命令模式
模型-视图-控制器(MVC) 是Cocoa的构建块之一,毫无疑问它是使用最频繁的设计模式。它根据通用的角色去划分类,这样就使得类的职责可以根据角色清晰的划分开来。
Model:模型保存应用程序的数据,定义了怎么去操作它。例如在本应用中模型就是Album类。
View: 视图是模型的可视化表示以及用户交互的控件;基本上来说,所有的UIView对象以及它的子类都属于视图。在本应用中AlbumView代表了视图。
Controller:控制器是一个协调所有工作的中介者(Mediator)。它访问模型中的数据并在视图中展示它们,同时它们还监听事件和根据需要操作数据。例如在本应用中ViewController。
模型会把任何数据的变更通知控制器,然后控制器更新视图数据。视图对象通知控制器用户的操作,控制器要么根据需要来更新模型,要么检索任何被请求的数据。
你可能在想为什么不能仅仅使用控制器,在一个类中实现视图和模型,这样貌似更加容易?
所有的这些都归结于代码关注点分离以及复用。在理想的状态下,视图应该和模型完全的分离。如果视图不依赖某个实际的模型,那么视图就可以被复用来展示不同模型的数据。
举个例子来说,如果将来你打算加入电影或者书籍到你的资料库中,你仍然可以使用同样的AlbumView去显示电影和书籍数据。更进一步来说,如果你想创建一个新的与专辑有关联的工程,你可以很简单的复用Album类,因为它不依赖任何视图。这就是MVC的强大之处。
单例设计模式确保对于一个给定的类只有一个实例存在,这个实例有一个全局唯一的访问点。它通常采用懒加载的方式在第一次用到实例的时候再去创建它。
苹果大量使用了此模式。例如:
[NSUserDefaults standardUserDefaults]
[UIApplication sharedApplication]
[UIScreen mainScreen]
[NSFileManager defaultManager]
有一些情况下,只有一个实例显得非常合理。举例来说,你不需要有多个Logger的实例,除非你想去写多个日志文件。或者一个全局的配置处理类:实现线程安全的方式访问共享实例是容易的,比如一个配置文件,有好多个类同时修改这个文件。
外观模式针对复杂的子系统提供了单一的接口,不需要暴漏一系列的类和API给用户,你仅仅暴漏一个简单统一的API。这个API的使用者完全不需要关心背后的复杂性。这个模式非常适合有一大堆很难使用或者理解的类的情况。外观模式解耦了使用系统的代码和需要隐藏的接口和实现类。它也降低了外部代码对内部子系统的依赖性。当隐藏在外观之后的类很容易发生变化的时候,此模式就很有用了,因为当背后的类发生变化的时候,外观类始终保持了同样的API。
本应用中用PersistencyManager本地保存专辑数据,使用HTTPClient处理远程连接,工程中的其它类暂时与本次实现的逻辑无关。
为了实现这个模式,只有LibraryAPI应该保存PersistencyManager和HTTPClient的实例,然后LibraryAPI将暴漏一个简单的API去访问这些服务。LibraryAPI将暴漏给其它代码,但是它隐藏了HTTPClient和PersistencyManager的复杂性。
装饰器模式在不修改原来代码的情况下动态的给对象增加新的行为和职责,它通过一个对象包装被装饰对象的方法来修改类的行为,这种方法可以做为子类化的一种替代方法。 在Objective-C中,存在两种非常常见的实现:Category(类别)和Delegation(委托)。
Category(类别)是一种不需要子类化就可以让你能动态的给已经存在的类增加方法的强有力的机制。新增的方法是在编译期增加的,这些方法执行的时候和被扩展的类的其它方法是一样的。它可能与装饰器设计模式的定义稍微有点不同,因为Category(类别)不会保存被扩展类的引用。
除了可以扩展你自己的类以外,还可以给Cocoa自己的类增加方法。
本应用中需要让Album(专辑)对象显示在一个表格视图(TableView)中:专辑的标题从何而来?因为专辑是模型对象,它本身不需要关心你如何显示它的数据。你需要增加一些代码去扩展专辑类的行为,但是不需要直接修改专辑类。创建一个专辑类扩展的类别,定义一个新的方法,这个方法会返回能很容易和UITableViews使用的数据结构。
当你使用UITableView的时候,你必须要实现tableView:numberOfRowsInSection:方法。 你不可能让UITableView知道它需要在每个区域显示多少行,因为这些是应用特定的数据。因此计算每个区域需要显示多少行的职责就给了UITableView的委托。这就让UITableView类独立于它要显示的数据。
UITableView的职责就是显示一个表格视图。然而最终它需要一些它自身没有的信息。那么它就求助于它的委托,通过发送消息给委托来获取信息。在Objective-C实现委托模式的时候,一个类可以通过协议(Protocol)来声明可选以及必要的方法。
这个是一个重要的模式。苹果在UIKit类中大量使用了它:UITableView, UITextView, UITextField, UIWebView, UIAlert, UIActionSheet, UICollectionView, UIPickerView,UIGestureRecognizer, UIScrollView等等等。
适配器可以让一些接口不兼容的类一起工作。它包装一个对象然后暴漏一个标准的交互接口。
苹果通过一个稍微不同的方式来实现它-苹果使用了协议的方式来实现。你可能已经熟悉UITableViewDelegate, UIScrollViewDelegate, NSCoding 和 NSCopying协议。举个例子,使用NSCopying协议,任何类都可以提供一个标准的copy方法。
在观察者模式中,一个对象任何状态的变更都会通知另外的对改变感兴趣的对象。这些对象之间不需要知道彼此的存在,这其实是一种松耦合的设计。当某个属性变化的时候,我们通常使用这个模式去通知其它对象。
此模式的通用实现中,观察者注册自己感兴趣的其它对象的状态变更事件。当状态发生变化的时候,所有的观察者都会得到通知。苹果的推送通知(Push Notification)就是一个此模式的例子。
如果你要遵从MVC模式的概念,你需要让模型对象和视图对象在不相互直接引用的情况下通信。这正是观察者模式的用武之地。
Cocoa通过通知(Notifications)和Key-Value Observing(KVO)来实现观察者模式。
不要和远程推送以及本地通知所混淆,通知是一种基于订阅-发布模式的模型,它让发布者可以给订阅者发送消息,并且发布者不需要对订阅者有任何的了解。
通知在苹果官方被大量的使用。举例来说,当键盘弹出或者隐藏的时候,系统会独立发送UIKeyboardWillShowNotification/UIKeyboardWillHideNotification通知。当你的应用进入后台运行的时候,系统会发送一个UIApplicationDidEnterBackgroundNotification通知。
在KVO中,一个对象可以要求在它自身或者其它对象的属性发送变化的时候得到通知。
KVO机制让对象可以感知到属性的变化。在本例中,你可以使用KVO去观察UIImageView的image属性的变化。
备忘录模式快照对象的内部状态并将其保存到外部。换句话说,它将状态保存到某处,过会你可以不破坏封装的情况下恢复对象的状态,也就是说原来对象中的私有数据仍然是私有的。
归档是苹果对于备忘录模式的特定实现之一。这种机制可以转换一个对象到一个可保存的数据流中,过会可以在不暴漏私有属性给外部的情况下重建它们。
命令模式将一个请求封装为一个对象。封装以后的请求会比原生的请求更加灵活,因为这些封装后的请求可以在多个对象之间传递,存储以便以后使用,还可以动态的修改,或者放进一个队列中。苹果通过Target-Action机制和Invocation实现命令模式。
你可以通过苹果的官方在线文档阅读更多关于Target-Action的内容,至于Invocation,它采用了NSInvocation类,这个类包含了一个目标对象,方法选择器,以及一些参数。这个对象可以动态的修改并且可以按需执行。实践中它是一个命令模式很好的例子。它解耦了发送对象和接受对象,并且可以保存一个或者多个请求。