IOS设计模式之二(门面模式,装饰器模式)

本文原文请见:http://www.raywenderlich.com/46988/ios-design-patterns.

由 @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,在文件头部增加下面的导入语句:

Objective-c代码  

  1. #import "Album.h"

接下来,在LibraryAPI.h中增加如下的方法定义:

Objective-c代码  

  1. - (NSArray*)getAlbums;

  2. - (void)addAlbum:(Album*)album atIndex:(int)index;

  3. - (void)deleteAlbumAtIndex:(int)index;

目前有一些你需要暴漏给其它类的方法。

打开LibraryAPI.m,增加如下的两个导入语句:

Objective-c代码  

  1. #import "PersistencyManager.h"

  2. #import "HTTPClient.h"

这里将是唯一的导入这两个类的地方。记住:你的API是对于复杂系统唯一的访问点。

现在,增加通过类扩展(class extension)增加一些私有的变量(在@implementation 行之上):

Objective-c代码  

  1. @interfaceLibraryAPI () {

  2. PersistencyManager *persistencyManager;

  3. HTTPClient *httpClient;

  4. BOOL isOnline;
  5. }

  6. @end

isOnline决定了是否服务器中任何专辑数据的改变应该被更新,例如增加或者删除专辑。

你现在需要通过init初始化这些变量。在LibraryAPI.m中增加如下的代码:

Objective-c代码  

  1. - (id)init{

  2. self = [super init];
  3. if (self) {
  4. persistencyManager = [[PersistencyManager alloc] init];
  5. httpClient = [[HTTPClient alloc] init];
  6. isOnline = NO;
  7. }
  8. return self;
  9. }

HTTP
客户端实际上不会真正的和一个服务器交互,它在这里仅仅是用来演示门面模式的使用,所以isOnline将总是NO。

接下来,增加如下的三个方法到LibraryAPI.m:

Objective-c代码  

  1. -(NSArray*)getAlbums

  2. {

  3. return [persistencyManager getAlbums];

  4. }
  5. - (void)addAlbum:(Album*)album atIndex:(int)index

  6. {

  7. [persistencyManager addAlbum:album atIndex:index];
  8. if (isOnline)
  9. {
  10. [httpClient postRequest:@"/api/addAlbum" body:[album description]];
  11. }
  12. }
  13. - (void)deleteAlbumAtIndex:(int)index

  14. {
  15. [persistencyManager deleteAlbumAtIndex:index];
  16. if (isOnline)

  17. {
  18. [httpClient postRequest:@"/api/deleteAlbum" body:[@(index) description]];
  19. }
  20. }

我们来看一看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类,新增如下的方法原型:

Objective-c代码  

  1. - (NSDictionary*)tr_tableRepresentation;

注意在方法开头有一个tr_前缀,它是类别TableRepresentation的缩写。再一次,这种约定也可以阻止和其它的方法冲突。

注意:如果方法与原来类的方法重名了,或者与同样的类(甚至它的父类)的其它的扩展重名,那么运行期到底应该调用哪个方法是未定义的。当你仅仅是在扩展你自己写的类时,这没什么问题,  但是当你在扩展标准的Cocoa 或者Cocoa
Touch类的时候,它可能会导致严重的问题。

打开Album+TableRepresentation.m,增加下面的方法:

Objective-c代码  

  1. - (NSDictionary*)tr_tableRepresentation

  2. {

  3. return @{@"titles":@[@"Artist", @"Album", @"Genre", @"Year"],

  4. @"values":@[self.artist, self.title, self.genre, self.year]};

  5. }

咋们稍停片刻来看看这个模式的强大之处:

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文件,在文件开头增加下面的导入语句:

Objective-c代码  

  1. #import "LibraryAPI.h"

  2. #import "Album+TableRepresentation.h"

现在,给类的扩展增加如下的私有变量,最终类的扩展如下所示:

Objective-c代码  

  1. @interfaceViewController () {

  2. UITableView *dataTable;

  3. NSArray *allAlbums;

  4. NSDictionary *currentAlbumData;

  5. int currentAlbumIndex;

  6. }
  7. @end

然后用下面的代码取代类型扩展中@interface一行:

Objective-c代码  

  1. @interface ViewController () <UITableViewDataSource, UITableViewDelegate> {

这就是如何使得委托符合协议,你可以把它认为是委托履行协议方法契约的约定。在这里,你指定ViewController将实现UITableViewDataSource和UITableViewDelegate协议。这种方式使得UITableView非常确定那些委托必须要实现的方法。

接下来,用如下代码取代viewDidLoad方法:

Objective-c代码  

  1. - (void)viewDidLoad

  2. {

  3. [super viewDidLoad];

  4. // 1

  5. self.view.backgroundColor = [UIColor colorWithRed:0.76f green:0.81f blue:0.87f alpha:1];

  6. currentAlbumIndex = 0;
  7. //2

  8. allAlbums = [[LibraryAPI sharedInstance] getAlbums];
  9. // 3

  10. // the uitableview that presents the album data

  11. dataTable = [[UITableView alloc] initWithFrame:CGRectMake(0, 120, self.view.frame.size.width, self.view.frame.size.height-120) style:UITableViewStyleGrouped];

  12. dataTable.delegate = self;

  13. dataTable.dataSource = self;

  14. dataTable.backgroundView = nil;

  15. [self.view addSubview:dataTable];

  16. }

下面我们来解释一下上面代码中标记了数字的地方:

1. 改变背景色为漂亮的藏青色

2. 通过API获取专辑数据。你不需要直接使用PersistencyManager。

3. 创建UITableView,声明viewController为UITableView的委托和数据源;因此viewController将提供所有的被UITableView需要的信息。

现在,在ViewController.m中增加如下的方法:

Objective-c代码  

  1. - (void)showDataForAlbumAtIndex:(int)albumIndex

  2. {

  3. // defensive code: make sure the requested index is lower than the amount of albums

  4. if (albumIndex < allAlbums.count)

  5. {

  6. // fetch the album

  7. Album *album = allAlbums[albumIndex];

  8. // save the albums data to present it later in the tableview

  9. currentAlbumData = [album tr_tableRepresentation];

  10. }

  11. else

  12. {

  13. currentAlbumData = nil;

  14. }
  15. // we have the data we need, let‘s refresh our tableview

  16. [dataTable reloadData];

  17. }

showDataForAlbumAtIndex:从专辑数组中获取需要的专辑数据。当你想去显示新的数据的时候,你仅仅需要调用reloadData.这将使得UITableView去问委托一些如下的信息:表格视图有多少个区域,每个区域应该显示多少行,每个单元格长什么样。

在viewDidLoad最后增加下面一行代码:

Objective-c代码  

  1. [self showDataForAlbumAtIndex:currentAlbumIndex];

上面的代码会在app启动的时候加载当前的专辑,因为currentAlbumIndex设置为0,所以它将显示第一个专辑。

构建并运行你的工程;你将得到一个崩溃信息,在调试控制台上面将显示如下的异常:

这里出了什么问题?你声明ViewController作为UITableView的委托和数据源,但是这样做了,也就意味着你必须实现所必须的方法-这些方法包括你还没有实现的tableView:numberOfRowsInSection:方法.

在ViewController.m文件中@implementation 和@end 之间的任何位置,增加下面的代码:

Objective-c代码  

  1. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

  2. {

  3. return [currentAlbumData[@"titles"] count];

  4. }
  5. - (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

  6. {

  7. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];

  8. if (!cell)

  9. {

  10. cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"cell"];

  11. }
  12. cell.textLabel.text = currentAlbumData[@"titles"][indexPath.row];

  13. cell.detailTextLabel.text = currentAlbumData[@"values"][indexPath.row];
  14. return cell;

  15. }

tableView:numberOfRowsInSection:方法返回表格视图需要显示的行数。这个和数据结构中的标题数量是匹配的。

tableView:cellForRowAtIndexPath:创建并返回一个包含标题和标题值的的单元格。

构建并运行你的工程,你的app应该会正常启动并显示下图所示的画面:

到目前为止进展挺顺利。但是你回忆第一张本app最终效果的图,你会发现在屏幕顶部有一个水平滚动视图在不同的专辑之间切换。并不是构建一个只为这次使用的单一目的的水平滚动视图,你为什么不做一个可以让任何视图复用的滚动视图呢?

为了使这个视图可以复用,应该由委托来决定所有的从左边开始依次到下一个对象的内容。水平滚动条应该声明那些能和它一起工作的委托方法,这有点类似UITableView的委托方法的方式。我们将在讨论下一个模式的时候来实现它。

IOS设计模式之二(门面模式,装饰器模式),布布扣,bubuko.com

时间: 2024-10-03 00:53:54

IOS设计模式之二(门面模式,装饰器模式)的相关文章

【一起学设计模式】状态模式+装饰器模式+简单工厂模式实战:(一)提交个订单我到底经历了什么鬼?

前言 之前在我的博客(一枝花算不算浪漫)中已经更新过两篇设计模式相关的内容 [一起学设计模式]策略模式实战一:基于消息发送的策略模式实战 [一起学习设计模式]策略模式实战二:配合注解 干掉业务代码中冗余的if else... [一起学设计模式]访问者模式实战:权限管理树删节点操作 [一起学设计模式]命令模式+模板方法+工厂方法实战: 如何优雅的更新商品库存... 上面内容都是基于真实业务场景精简后的设计(工作中真实场景使用到的). 之前为了学习设计模式,看过网上很多相关博客讲解,大都是画下UML

设计模式(三):装饰器模式

一.概述 装饰器模式动态地将责任附加到对象上.想要扩展功能,装饰者提供了有别于继承的另一种选择.简单描述就是包装对象,让对象提供新的行为. 二.解决问题 当一个类想要获得一个行为,我们会想到面向对象四大特性之一的继承,继承能够让子类从父类中获得行为,实现很好的代码复用.但这种继承而来的行为是在编译时静态决定的,而且所有的子类都会继承相同的行为.如果我们想要扩展对象的行为,就要创建一个子类来修改父类的方法(也就是覆盖父类行为),每扩展一个行为就要创建一个子类,这样会带来很多问题.第一,如果需要扩展

菜鸟版JAVA设计模式—从火锅底料到装饰器模式

今天开始学历了JAVA设计模式中的装饰模式,照例还是写下自己理解的心得吧. 装饰器模式,啥是装饰器模式?带着这个问题,咱们好好的去火锅店吃个火锅. "老板,来份全辣锅底,不要给我用装饰器模式来配料!"我特地狠狠的强调了最后一句话. 不到一会,老板给我端来了一个火锅和几个盘子,火锅里装了盐水,而盘子里放了辣椒,花椒,茴香,大蒜等佐料.......... 这时候大家可能就需要问了,这咋吃啊...难道让我自己配料么? 这便是是我们的矛盾了!客户需要的一盘已经配好料的火锅汤底,但是我们初期给用

PHP设计模式 五 (代理模式 装饰器模式)

代理模式 在客户端和实体之间建立一个代理对象,客户端对实体的操作全部委派给代理对象,隐藏实体具体实现细节. Proxy还可以与业务代码分离,部署到另外的服务器,业务代码中通过RPC来委派任务. 代理Proxy.php: <?php namespace Components\Proxy; class Proxy implements IUserProxy { function get($id) { $db = \Components\Register::get('slave'); $db->qu

结构型模式-装饰器模式

对原有对象进行修饰,如有一个篮子,现在对篮子进行修饰,放入苹果,香蕉,橙子 package constructional.pattern.decorator; /* * 创建一个对象的抽象也就是接口 */ public interface Basket { public void show(); } package constructional.pattern.decorator; /** *身份:被装饰的对象 *一个对接口的实现,这个对象表示要我们将来要修饰的篮子里装内容,如果想修饰篮子的造型

结构型模式————装饰器模式(3.1)

什么是装饰器模式? [先吃三颗栗子:] 1.PC=主机+显示器+键盘+鼠标+鼠标垫 主机是核心,而其他的组成部分都是装饰. 2.手抓饼=饼+鸡蛋+培根+黄瓜 饼是核心,鸡蛋,培根是可选的,可以理解为装饰. 3.咖啡=咖啡+牛奶+冰+方糖 咖啡是核心,牛奶等可选. 比喻虽然形象生动,但是与实际或多或少会产生偏差. 抽象的解释:装饰器模式的目的--核心部分和装饰部分可以自由组合. 装饰器模式的功能 对于软件开发来说,聚焦于软件的灵活性和可扩展性. 装饰器模式要求: 装饰可选 装饰可扩展 核心部分可扩

Python3-设计模式-装饰器模式

装饰器模式 动态的给原有对象添加一些额外的职责,面向切面编程(AOP),多用于和主业务无关,但又必须的业务,如:登录认证.加锁.权限检查等 Python代码实现示例 需求点: 1.在old_func()的输出前面输出Hello,everyone! 2.在old_func()的输出后面输出Thank you! 要求: 1.不能改变原调用方式 2.要遵循开闭原则,不能修改函数内的代码 # 原有函数 def old_func(name, age): print("My name is %s,I'm %

Decorator模式 装饰器模式

Android 使用了装饰器模式 1. 概述 若你从事过面向对象开发,实现给一个类或对象增加行为,使用继承机制,这是所有面向对象语言的一个基本特性.如果已经存在的一个类缺少某些方法,或者须要给方法添加更多的功能(魅力),你也许会仅仅继承这个类来产生一个新类—这建立在额外的代码上. 通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法.但是这种方法是静态的,用户不能控制增加行为的方式和时机.如果  你希望改变一个已经初始化的对象的行为,你怎么办?或者,你希望继承许多类的行为,改怎么办

设计模式总结篇系列:装饰器模式(Decorator)

在面向对象设计过程中,经常会遇到需要对现有的类的功能进行扩展,通常我们可以采用继承的方式.例如老罗最近在做手机,一开始需要定义手机所应具有的功能: 1 interface Phone{ 2 3 public void tel(); 4 5 public void sms(); 6 7 } 在此,为简单起见,只是定义了接打电话和收发短信功能. 然后,老罗开始造手机,经过两年艰苦努力,第一代手机T1终于面世了,很高兴的开了发布会,反响还不错. 1 class T1 implements Phone{

十二、Decorator 装饰器模式

设计: 代码清单: Display public abstract class Display { public abstract int getColumns(); public abstract int getRows(); public abstract String getRowText(int row); public final void show(){ for(int i=0;i<getRows();i++){ System.out.println(getRowText(i));