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. }  
  7. @end  

 

isOnline决定了是否服务器中任何专辑数据的改变应该被更新,例如增加或者删除专辑。
你现在需要通过init初始化这些变量。在LibraryAPI.m中增加如下的代码:

 

Objective-c代码  收藏代码
  1. - (id)init{  
  2.     self = [super init];  
  3.   
  4.     if (self) {  
  5.   
  6.         persistencyManager = [[PersistencyManager alloc] init];  
  7.   
  8.         httpClient = [[HTTPClient alloc] init];  
  9.   
  10.         isOnline = NO;  
  11.   
  12.     }  
  13.   
  14.     return self;  
  15.   
  16. }  

 

 

HTTP 客户端实际上不会真正的和一个服务器交互,它在这里仅仅是用来演示门面模式的使用,所以isOnline将总是NO。
接下来,增加如下的三个方法到LibraryAPI.m:

 

Objective-c代码  收藏代码
  1.  -(NSArray*)getAlbums  
  2. {  
  3.     return [persistencyManager getAlbums];  
  4. }  
  5.   
  6.    
  7.   
  8. - (void)addAlbum:(Album*)album atIndex:(int)index  
  9. {  
  10.     [persistencyManager addAlbum:album atIndex:index];  
  11.   
  12.     if (isOnline)  
  13.   
  14.     {  
  15.   
  16.         [httpClient postRequest:@"/api/addAlbum" body:[album description]];  
  17.   
  18.     }  
  19.   
  20. }  
  21.   
  22.    
  23.   
  24. - (void)deleteAlbumAtIndex:(int)index  
  25. {  
  26.   
  27.     [persistencyManager deleteAlbumAtIndex:index];  
  28.   
  29.     if (isOnline)  
  30.     {  
  31.   
  32.         [httpClient postRequest:@"/api/deleteAlbum" body:[@(index) description]];  
  33.   
  34.     }  
  35.   
  36. }  
 

 

我们来看一看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.    
  8.    @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.    
  8.     //2  
  9.     allAlbums = [[LibraryAPI sharedInstance] getAlbums];  
  10.    
  11.     // 3  
  12.     // the uitableview that presents the album data  
  13.     dataTable = [[UITableView alloc] initWithFrame:CGRectMake(0120, self.view.frame.size.width, self.view.frame.size.height-120) style:UITableViewStyleGrouped];  
  14.     dataTable.delegate = self;  
  15.     dataTable.dataSource = self;  
  16.     dataTable.backgroundView = nil;  
  17.     [self.view addSubview:dataTable];  
  18. }  
 
下面我们来解释一下上面代码中标记了数字的地方:
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.    
  16.     // we have the data we need, let's refresh our tableview     
  17.     [dataTable reloadData];  
  18. }  
 
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.    
  6. - (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  
  7. {  
  8.     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];  
  9.     if (!cell)  
  10.     {  
  11.         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"cell"];  
  12.     }  
  13.    
  14.     cell.textLabel.text = currentAlbumData[@"titles"][indexPath.row];  
  15.     cell.detailTextLabel.text = currentAlbumData[@"values"][indexPath.row];  
  16.    
  17.     return cell;  
  18. }  
 
tableView:numberOfRowsInSection:方法返回表格视图需要显示的行数。这个和数据结构中的标题数量是匹配的。
tableView:cellForRowAtIndexPath:创建并返回一个包含标题和标题值的的单元格。
构建并运行你的工程,你的app应该会正常启动并显示下图所示的画面:


 
到目前为止进展挺顺利。但是你回忆第一张本app最终效果的图,你会发现在屏幕顶部有一个水平滚动视图在不同的专辑之间切换。并不是构建一个只为这次使用的单一目的的水平滚动视图,你为什么不做一个可以让任何视图复用的滚动视图呢?
为了使这个视图可以复用,应该由委托来决定所有的从左边开始依次到下一个对象的内容。水平滚动条应该声明那些能和它一起工作的委托方法,这有点类似UITableView的委托方法的方式。我们将在讨论下一个模式的时候来实现它。

转载于:https://www.cnblogs.com/sanjianghuiliu/p/3663918.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/407875.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1.使用 Blazor 利用 ASP.NET Core 生成第一个 Web 应用

参考 https://dotnet.microsoft.com/zh-cn/learn/aspnet/blazor-tutorial/create 1.使用vs2022创建新项目 选择 C# -> Windows -> Blzxor Server 应用模板 2.项目名称BlazorApp下一步 3.选择 .NET6.0 或 .NET7.0 或 .NET8.0 创建 4.运行BlazorApp 5.全部选择是。 信…

BROCADE 300和MD3200扩展柜FC SAN,截图

这表示俺玩过&#xff0c;其实&#xff0c;这个光交换机在只有一个共享存储的情况下&#xff0c;可用可不用。 FC BROCADE只是为了方便后期扩展。 FC SAN之类的识别不靠IP&#xff0c;因为不是IP SAN嘛。但也是自己的识别体系。 转载于:https://www.cnblogs.com/aguncn/p/36640…

总算有点眉目了!

今天打开一个很久没用的邮箱~发现51CTO居然还记得我给我发了封邮件~我也很久没去了&#xff08;基本上注册过后就没去&#xff09;进去看了下~发现变化好大......壮大了。现在我有点激动&#xff0c;总算找到点眉目&#xff0c;在经过了2年的等死阶段&#xff08;偶于2005年学校…

C++ 20 并发编程 std::promise

C 20 并发编程 std::promise std::promise和std::future是一对, 通过它们可以进行更加灵活的任务控制 promise通过函数set_value()传入一个值, 异常, 或者通知, 并异步的获取结果 例子: void product(std::promise<int>&& intPromise, int a, int b) {intPro…

女孩儿们的那点秘密

女孩终归是女孩&#xff0c;女孩们总是强迫自己要出得了厅堂入得了厨房有木有&#xff1f;可你们造吗&#xff1f;这对女孩来说可都不容易&#xff01; 你们都以为做饭对女孩来说很简单&#xff0c;可知道我们面对四处乱喷热滚滚的油也会手足无措躲得远远的&#xff1f; 别嫌弃…

bootstrap学习笔记(一)网络系统

注&#xff1a;我是根据自己理解写的 有参考http://www.w3cschool.cc/bootstrap该教程。 bootstrap目前有两种默认的网络&#xff08;格&#xff09;系统&#xff1a;一个是940px,另一个是12列。 我们先从12列入手看看&#xff1a; 1.Bootstrap 使用 CSS 的 class "row&q…

委托笔记

刚写了段程序&#xff0c;总算稍微理解了下委托&#xff0c;留下备忘。 定义&#xff1a; 1 publicdelegatestringDelegateTest(stringsss);类中实例化委托&#xff1a; 1 publicclassTestClass2 {3 publiceventDelegateTest _delegateTest;4 5 publicstringBeginDelegate()6 {…

C++ 20 协程总结

C 20 协程总结 介绍 C 20提供的是非对称的、一等对象、无栈的协程&#xff08;Coroutines in C20 are asymmetric, first-class, and stackless&#xff09; 所谓协程&#xff0c;即用户级线程&#xff0c;一种用于将异步代码同步化的编程机制&#xff0c;使得程序的执行流可…

.net程序员的盲点(八):泛型

1.泛型介绍泛型类和泛型方法同时具备可重用性、类型安全和效率&#xff0c;这是非泛型类和非泛型方法无法具备的。泛型通常用在集合和在集合上运行的方法中。.NET Framework 2.0 版类库提供一个新的命名空间System.Collections.Generic&#xff0c;其中包含几个新的基于泛型的集…

Sublime Text 插件之常用20个插件

作为一个开发者你不可能没听说过 Sublime Text。不过你没听说过也没关系&#xff0c;下面让你明白。 Sublime Text是一款非常精巧的文本编辑器&#xff0c;适合编写代码、做笔记、写文章。它用户界面十分整洁&#xff0c;功能非同凡响&#xff0c;性能快得出奇。这些非常棒的特…

JUnit 4 与 JUnit 3

JUnit 是 Java? 语言事实上的 标准单元测试库。JUnit 4 是该库三年以来最具里程碑意义的一次发布。它的新特性主要是通过采用 Java 5 中的标记&#xff08;annotation&#xff09;而不是利用子类、反射或命名机制来识别测试&#xff0c;从而简化测试。在本文中&#xff0c;执着…

整合quickx到普通cocos2dx

quickx是对cocos2dx的lua扩展&#xff0c;它做了一些C的扩展&#xff0c;同时还在lua做了一些封装&#xff0c; 让用lua开发cocos2dx更快&#xff0c;中文站http://quick.cocoachina.com/。 由于现在的项目对cocos2dx有一些修改&#xff0c;又想用到quickx的便捷&#xff0c;于…

我的项目-财务系统

4 名称&#xff1a;财务管理系统 时间&#xff1a;2000 用时&#xff1a;3个月 vb6sqlserver7 独立完成 描述&#xff1a;包含凭证输入&#xff0c;审核&#xff0c;记帐&#xff0c;帐簿管理&#xff0c;自动转帐&#xff0c;会计报表等财务管理的整个流程。此项目在兖州…

对二维数组进行Zig-Zag扫描(C++)

对二维数组进行Zig-Zag扫描(C)&#xff0c;先自定义了一个类&#xff0c;类中有个函数Run()来实现这个扫描过程&#xff0c;二维数组是动态分配空间以及随机赋值的。 下图是Zig-Zag扫描方式&#xff1a; CZigZag.h: #include<iostream>using namespace std; typedef s…

Storing and Retrieving Images from SQL Server using Microsoft .NET

Storing and Retrieving Images from SQL Server using Microsoft .NET 原文 Storing and Retrieving Images from SQL Server using Microsoft .NET Download source - 19.6 KbIntroduction This article is about storing and retrieving images from database in Microsoft …

flot绘制折线图

<!--请先确保你有jquery.js 和 jquery.flot.min.js--> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns"http://www.w3.org/1999/xhtm…

在dos下运行.exe程序(C++)

说明&#xff1a;在Dos下运行.exe程序(C) 先看C源文件&#xff1a; #include<iostream>using namespace std; void main(int argc, char * argv[]){ cout<<"argc "<<argc<<endl; for(int i 0; i < argc; i) cout<<argv[i]<…

提取二维矩阵中分块后指定的块

对一个二维矩阵I(NN)进行分块(块大小为nn),并提取其中第ii块中的元素 % 对二维矩阵I进行[n n]分块&#xff0c;取其中第ii块中的元素function x getBlock(I, n, ii) N size(I, 1); n1 N / n; n2 n * n; [a, b] ind2sub([n1 n1], ii); p (b-1) * n * (n1 *…