由 @krq_tiger(http://weibo.com/xmuzyq)翻译,如果你发现有什么错误,请与我联系谢谢。
门面(Facade)模式(译者注:facade有些书籍译为门面,有些书籍译为外观,此处译为门面)
门面模式针对复杂的子系统提供了单一的接口,不需要暴漏一些列的类和API给用户,你仅仅暴漏一个简单统一的API。
下面的图解释了这个概念:
这个API的使用者完全不需要关心背后的复杂性。这个模式非常适合有一大堆很难使用或者理解的类的情况。
门面模式解耦了使用系统的代码和需要隐藏的接口和实现类。它也降低了外部代码对内部子系统的依赖性。当隐藏在门面之后的类很容易发生变化的时候,此模式就很有用了,因为当背后的类发生变化的时候,门面类始终保持了同样的API。
举个例子来说,如果有一天你想取代后端服务,你不需要改变API的使用者,因为API没有发生变化。
如何使用门面模式
当前你已经用PersistencyManager本地保存专辑数据,使用HTTPClient处理远程连接,工程中的其它类暂时与本次实现的逻辑无关。
为了实现这个模式,只有LibraryAPI应该保存PersistencyManager和HTTPClient的实例,然后LibraryAPI将暴漏一个简单的API去访问这些服务。
注意: 通常来说,单例类的生命周期贯穿于整个应用的生命周期中,你不应对保存太多其它对象的强引用,因为他们只有到应用关闭的时候才能被释放。
本次设计看起来像下图:
LibraryAPI将暴漏给其它代码,但是它隐藏了HTTPClient和PersistencyManager的复杂性。
打开LibraryAPI.h,在文件头部增加下面的导入语句:
#import "Album.h"
接下来,在LibraryAPI.h中增加如下的方法定义:
- (NSArray*)getAlbums; - (void)addAlbum:(Album*)album atIndex:(int)index; - (void)deleteAlbumAtIndex:(int)index;
目前有一些你需要暴漏给其它类的方法。
打开LibraryAPI.m,增加如下的两个导入语句:
#import "PersistencyManager.h" #import "HTTPClient.h"
这里将是唯一的导入这两个类的地方。记住:你的API是对于复杂系统唯一的访问点。
现在,增加通过类扩展(class extension)增加一些私有的变量(在@implementation 行之上):
@interfaceLibraryAPI () { PersistencyManager *persistencyManager; HTTPClient *httpClient; BOOL isOnline; } @end
isOnline决定了是否服务器中任何专辑数据的改变应该被更新,例如增加或者删除专辑。
你现在需要通过init初始化这些变量。在LibraryAPI.m中增加如下的代码:
- (id)init{ self = [super init]; if (self) { persistencyManager = [[PersistencyManager alloc] init]; httpClient = [[HTTPClient alloc] init]; isOnline = NO; } return self; }
HTTP 客户端实际上不会真正的和一个服务器交互,它在这里仅仅是用来演示门面模式的使用,所以isOnline将总是NO。
接下来,增加如下的三个方法到LibraryAPI.m:
-(NSArray*)getAlbums { return [persistencyManager getAlbums]; } - (void)addAlbum:(Album*)album atIndex:(int)index { [persistencyManager addAlbum:album atIndex:index]; if (isOnline) { [httpClient postRequest:@"/api/addAlbum" body:[album description]]; } } - (void)deleteAlbumAtIndex:(int)index { [persistencyManager deleteAlbumAtIndex:index]; if (isOnline) { [httpClient postRequest:@"/api/deleteAlbum" body:[@(index) description]]; } }
我们来看一看addAlbum:atIndex:.这个类首先更新本地的数据,然后如果有网络连接,它更新远程服务器。这就是门面模式的强大之处。当某些外部的类增加一个新的专辑的时候,它不知道也不需要知道背后的复杂性。
注意:当为子系统的类设计门面的时候,要记住:任何东西都不能阻止客户端直接访问这些隐藏的类。不要对这些防御性的代码太过于吝啬,并且也不要假设所有的客户端都会和门面一样使用你的类。
构建并运行你的应用。你将看到一个激动人心的空白的黑屏(哈哈):
接下来,你将需要在屏幕上显示专辑数据,使用你的下个设计模式-装饰器设计模式将是非常好的选择。
装饰器(Decorator)模式
装饰器模式在不修改原来代码的情况下动态的给对象增加新的行为和职责,它通过一个对象包装被装饰对象的方法来修改类的行为,这种方法可以做为子类化的一种替代方法。
在Objective-C中,存在两种非常常见的实现:Category(类别)和Delegation(委托)。
Category(类别)
Category(类别)是一种不需要子类化就可以让你能动态的给已经存在的类增加方法的强有力的机制。新增的方法是在编译期增加的,这些方法执行的时候和被扩展的类的其它方法是一样的。它可能与装饰器设计模式的定义稍微有点不同,因为Category(类别)不会保存被扩展类的引用。
注意: 你除了可以扩展你自己的类以外,你还可以给Cocoa自己的类增加方法。
如何使用类别
设想一种情况,你需要让Album(专辑)对象显示在一个表格视图(TableView)中:
专辑的标题从何而来?因为专辑是模型对象,它本身不需要关心你如何显示它的数据。你需要增加一些代码去扩展专辑类的行为,但是不需要直接修改专辑类。
你将创建一个专辑类扩展的类别,它将定义一个新的方法,这个方法会返回能很容易和UITableViews使用的数据结构。这个数据结构如下图所示:
为了给Album增加一个类别,导航到“File\New\File...\",选择Objective-C category模板,不要习惯性的选择Objective-C class模板。在Category域输入TableRepresentation,Category on域输入Album.
注意:你已经注意到了新建文件的名字了吗?Album+TableRepresentation意味着你正在扩展Album类。这种约定是非常重要,因为它方便阅读以及阻止和你或者其他人创建的类别冲突。
打开Album+TableRepresentation.h类,新增如下的方法原型:
- (NSDictionary*)tr_tableRepresentation;
注意在方法开头有一个tr_前缀,它是类别TableRepresentation的缩写。再一次,这种约定也可以阻止和其它的方法冲突。
注意:如果方法与原来类的方法重名了,或者与同样的类(甚至它的父类)的其它的扩展重名,那么运行期到底应该调用哪个方法是未定义的。当你仅仅是在扩展你自己写的类时,这没什么问题, 但是当你在扩展标准的Cocoa 或者Cocoa Touch类的时候,它可能会导致严重的问题。
打开Album+TableRepresentation.m,增加下面的方法:
- (NSDictionary*)tr_tableRepresentation { return @{@"titles":@[@"Artist", @"Album", @"Genre", @"Year"], @"values":@[self.artist, self.title, self.genre, self.year]}; }
咋们稍停片刻来看看这个模式的强大之处:
1. 你可以直接使用Album的属性
2. 你不需要子类化就可以增加方法。当然如果你想子类化Album,你任然可以那样做。
3. 简简单单的几句代码就让你在不修改Album的情况下,返回了一个UITableView风格的Album。
苹果在Foundation类中大量使用了Category。想知道他们是怎么做的,你可以代开NSString.h文件,找到@interface NSString,你将看到类和其它三个类别的定义:NSStringExtensionMethods,NSExtendedStringPropertyListParsing,NSStringDeprecated.类别让方法组织成相互独立的部分。
Delegation(委托)
委托作为另外一个装饰器模式,它是一种和其它对象交互的机制。举例来说,当你使用UITableView的时候,你必须要实现tableView:numberOfRowsInSection:方法。
你不可能让UITableView知道它需要在每个区域显示多少行,因为这些是应用特定的数据。因此计算每个区域需要显示多少行的职责就给了UITableView的委托。这就让UITableView类独立于它要显示的数据。
这里通过一个图来解释当你创建UITableView的时候会发生什么:
UITableView的职责就是显示一个表格视图。然而最终它需要一些它自身没有的信息。那么它就求助于它的委托,通过发送消息给委托来获取信息。在Objective-C实现委托模式的时候,一个类可以通过协议(Protocol)来声明可选以及必要的方法。本指南稍后会涉及协议方面的内容。
子类化一个对象,复写需要的方法看起来好像更容易一点,但是考虑到你只能子类化一个类,如果你想一个对象作为两个或者更多对象的委托的话,使用子类化将不能实现。
注意:这个是一个重要的模式。苹果在UIKit类中大量使用了它:UITableView, UITextView, UITextField, UIWebView, UIAlert, UIActionSheet, UICollectionView, UIPickerView,UIGestureRecognizer, UIScrollView等等等。
如何使用委托模式
打开ViewController.m文件,在文件开头增加下面的导入语句:
#import "LibraryAPI.h" #import "Album+TableRepresentation.h"
现在,给类的扩展增加如下的私有变量,最终类的扩展如下所示:
@interfaceViewController () { UITableView *dataTable; NSArray *allAlbums; NSDictionary *currentAlbumData; int currentAlbumIndex; } @end
然后用下面的代码取代类型扩展中@interface一行:
@interface ViewController () <UITableViewDataSource, UITableViewDelegate> {
这就是如何使得委托符合协议,你可以把它认为是委托履行协议方法契约的约定。在这里,你指定ViewController将实现UITableViewDataSource和UITableViewDelegate协议。这种方式使得UITableView非常确定那些委托必须要实现的方法。
接下来,用如下代码取代viewDidLoad方法:
- (void)viewDidLoad { [super viewDidLoad]; // 1 self.view.backgroundColor = [UIColor colorWithRed:0.76f green:0.81f blue:0.87f alpha:1]; currentAlbumIndex = 0; //2 allAlbums = [[LibraryAPI sharedInstance] getAlbums]; // 3 // the uitableview that presents the album data dataTable = [[UITableView alloc] initWithFrame:CGRectMake(0, 120, self.view.frame.size.width, self.view.frame.size.height-120) style:UITableViewStyleGrouped]; dataTable.delegate = self; dataTable.dataSource = self; dataTable.backgroundView = nil; [self.view addSubview:dataTable]; }
下面我们来解释一下上面代码中标记了数字的地方:
1. 改变背景色为漂亮的藏青色
2. 通过API获取专辑数据。你不需要直接使用PersistencyManager。
3. 创建UITableView,声明viewController为UITableView的委托和数据源;因此viewController将提供所有的被UITableView需要的信息。
现在,在ViewController.m中增加如下的方法:
- (void)showDataForAlbumAtIndex:(int)albumIndex { // defensive code: make sure the requested index is lower than the amount of albums if (albumIndex < allAlbums.count) { // fetch the album Album *album = allAlbums[albumIndex]; // save the albums data to present it later in the tableview currentAlbumData = [album tr_tableRepresentation]; } else { currentAlbumData = nil; } // we have the data we need, let's refresh our tableview [dataTable reloadData]; }
showDataForAlbumAtIndex:从专辑数组中获取需要的专辑数据。当你想去显示新的数据的时候,你仅仅需要调用reloadData.这将使得UITableView去问委托一些如下的信息:表格视图有多少个区域,每个区域应该显示多少行,每个单元格长什么样。
在viewDidLoad最后增加下面一行代码:
[self showDataForAlbumAtIndex:currentAlbumIndex];
上面的代码会在app启动的时候加载当前的专辑,因为currentAlbumIndex设置为0,所以它将显示第一个专辑。
构建并运行你的工程;你将得到一个崩溃信息,在调试控制台上面将显示如下的异常:
这里出了什么问题?你声明ViewController作为UITableView的委托和数据源,但是这样做了,也就意味着你必须实现所必须的方法-这些方法包括你还没有实现的tableView:numberOfRowsInSection:方法.
在ViewController.m文件中@implementation 和@end 之间的任何位置,增加下面的代码:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [currentAlbumData[@"titles"] count]; } - (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"cell"]; } cell.textLabel.text = currentAlbumData[@"titles"][indexPath.row]; cell.detailTextLabel.text = currentAlbumData[@"values"][indexPath.row]; return cell; }
tableView:numberOfRowsInSection:方法返回表格视图需要显示的行数。这个和数据结构中的标题数量是匹配的。
tableView:cellForRowAtIndexPath:创建并返回一个包含标题和标题值的的单元格。
构建并运行你的工程,你的app应该会正常启动并显示下图所示的画面:
到目前为止进展挺顺利。但是你回忆第一张本app最终效果的图,你会发现在屏幕顶部有一个水平滚动视图在不同的专辑之间切换。并不是构建一个只为这次使用的单一目的的水平滚动视图,你为什么不做一个可以让任何视图复用的滚动视图呢?
为了使这个视图可以复用,应该由委托来决定所有的从左边开始依次到下一个对象的内容。水平滚动条应该声明那些能和它一起工作的委托方法,这有点类似UITableView的委托方法的方式。我们将在讨论下一个模式的时候来实现它。
相关推荐
在iOS中,UIAppearance协议允许我们为UIControl的子类添加全局的外观配置,这就是装饰器模式的应用。 6. **代理模式的变种:Block Delegate**:在某些情况下,使用Block(闭包)作为委托方法可以使代码更加简洁和易...
书中详细介绍了多种iOS设计模式,包括单例模式、工厂模式、建造者模式、代理模式、装饰器模式、观察者模式、策略模式、状态模式、模版方法模式和访问者模式。每个模式都配有实际的Objective-C代码示例,帮助读者理解...
在iOS开发中,设计模式是一种解决常见编程问题的模板或最佳实践,它们是经验丰富的...Objective-C编程之道:iOS设计模式解析这本书可能详细介绍了这些模式的原理和在实际项目中的应用,是一份非常有价值的学习资源。
本书《Pro Design Patterns in Swift》以iOS Swift开发为背景,详尽地展示了如何利用Swift语言的特点来应用最核心、持久的设计模式,提升应用程序的结构和可扩展性。设计模式在软件开发中扮演着至关重要的角色,它们...
在iOS开发中,设计模式是一种解决常见编程问题的模板或最佳实践,它们是经验丰富的开发者在长期实践中总结出的解决方案。"iOS设计模式解析"这个主题涵盖了如何在Objective-C编程中应用这些模式来提高代码质量、可...
在iOS开发中,设计模式是一种重要的编程思想,它将实践中常用的设计策略抽象出来,以便于复用和提高代码质量。本文将深入探讨一种常见的设计模式——原型模式(Prototype Pattern),并结合具体的iOS应用场景进行...
最全最新版 Objective-C编程之道IOS设计模式解析.pdf
iOS设计模式解析.pdf
解析iOS设计模式的开山之作 优化Objective-C编程实践的必修宝典 由此迈入移动开发高手行列 本书讲述如何在代码中应用创建型模式、结构型模式和行为模式等,如何设计模式以巩固应用程序,并通过设计模式实例介绍MVC...
### iOS设计模式详解 #### 一、单例模式 单例模式是一种常用的设计模式,它的主要目的是确保一个类仅有一个实例,并且该实例能够被全局访问。这种模式非常适合那些需要频繁访问并保持状态一致的对象,比如全局配置...
在iOS开发中,设计模式是解决常见编程问题的模板或最佳实践,它们为软件设计师提供了在特定上下文中解决常见问题的通用解决方案。本资源“ios设计模式开发23种设计模式OC编程”提供了Objective-C(OC)语言实现的...
- 装饰器:装饰器模式动态地给对象增加新的功能,而不改变其原有的结构。在iOS中,可以用来扩展UI控件的功能,如自定义UILabel,增加动画效果。 - 适配器:适配器模式使两个不兼容的接口能够协同工作。在iOS中,...
在iOS开发中,设计模式是解决常见编程问题的模板,它们提供了一种标准的方法来组织代码,使得代码更易于理解、扩展和维护。抽象工厂模式是设计模式中的一种,尤其适用于创建一组相关或相互依赖的对象。这个实例Demo...
在iOS开发中,常见的设计模式有单例模式、工厂模式、代理模式、观察者模式、装饰模式、策略模式、建造者模式等。这些模式在不同场景下各有优势,能够帮助开发者更好地组织代码。 2. **架构设计**:iOS应用的架构...
iOS设计模式解析-Object编程之道
在iOS应用开发中,设计模式是提升代码可读性、可维护性和可扩展性的重要工具。以下是关于三种主要设计模式——MVC(Model-View-Controller)、MVP(Model-View-Presenter)和MVVM(Model-View-ViewModel)的详细讲解...
装饰模式是一种设计模式,主要目的是在不改变对象原有结构的情况下,动态地为对象添加新的功能。在iOS开发中,装饰模式被广泛应用于扩展类的功能,尤其是当需要在运行时为对象添加行为时,它提供了非常灵活的解决...
Objective-C 编程之道 IOS 设计模式 解析 你值得拥有 come on baby
装饰器模式是一种结构型设计模式,它允许在不修改对象本身的情况下动态地为对象添加新的行为或职责。这种模式在软件工程中广泛应用,特别是在需要扩展已有功能而不影响原有代码结构时。在iOS开发中,装饰器模式同样...
**Model-View-Controller**(简称MVC)是一种广泛应用于软件工程中的设计模式,在iOS开发中更是被视为核心设计模式之一。该模式的核心理念是将应用程序分为三个相互关联的组成部分: - **Model(模型)**:这部分...