音效素材网提供各类素材,打造精品素材网站!

站内导航 站长工具 投稿中心 手机访问

音效素材

深入解析设计模式中的装饰器模式在iOS应用开发中的实现
日期:2016-03-30 11:58:49   来源:脚本之家

装饰器模式可以在不修改代码的情况下灵活的为一对象添加行为和职责。当你要修改一个被其它类包含的类的行为时,它可以代替子类化方法。

一、基本实现
下面我把类的结构图向大家展示如下:

2016330115441751.jpg (500×318)

让我们简单分析一下上面的结构图,Component是定义一个对象接口,可以给这些对象动态地添加职责。ConcreteComponent是定义了一个具体的对象,也可以给这个对象添加一些职责。Decorator,装饰抽象类,继承了Component,从外类来扩展Component类的功能,但对于Component来说,是无需知道Decorator的存在的。至于ConcreteDecorator就是具体的装饰对象,起到给Component添加职责的功能。

下面,还是老套路,我会尽可能的给出Objective C实现的最简单的实例代码,首先声明一下,这些代码是运行在ARC环境下的,所以对于某些可能引起内存泄漏的资源并没有采用手动释放的方式,这一点还是需要大家注意。

注意:本文所有代码均在ARC环境下编译通过。

Components类接口文件

复制代码 代码如下:

#import<Foundation/Foundation.h>    

@interface Components :NSObject
-(void) Operation;
@end


Components类实现文件

#import"Components.h"

复制代码 代码如下:

@implementation Components
-(void)Operation{
    return;
}
@end

Decorator类接口文件

#import"Components.h"  

复制代码 代码如下:

@interface Decorator :Components{
@protected Components *components;
}
-(void)SetComponents:(Components*)component;
@end

Decorator类实现文件

#import"Decorator.h"

复制代码 代码如下:

@implementation Decorator
-(void)SetComponents:(Components*)component{
    components = component;
}
-(void)Operation{
    if(components!=nil){
        [components Operation];
    }
}
@end

ConcreteComponent类接口文件
复制代码 代码如下:

#import"Components.h"

@interface ConcreteComponent :Components
@end


ConcreteComponent类实现文件
复制代码 代码如下:

#import "ConcreteComponent.h"

@implementation ConcreteComponent
-(void)Operation{
    NSLog(@"具体操作的对象");
}
@end


ConcreteDecoratorA类接口文件
复制代码 代码如下:

#import "ConcreteDecoratorA.h"
#import "Decorator.h"

@interface ConcreteDecoratorA :Decorator
@end


ConcreteDecoratorA类实现文件

#import"ConcreteDecoratorA.h"

复制代码 代码如下:

@implementation ConcreteDecoratorA
-(void)Operation{
    NSLog(@"具体装饰对象A的操作");
    [super Operation];
}
@end

ConcreteDecoratorB类接口文件

#import "Decorator.h"

复制代码 代码如下:

@interface ConcreteDecoratorB :Decorator
@end

ConcreteDecoratorB类实现文件
复制代码 代码如下:

#import "ConcreteDecoratorB.h"

@implementation ConcreteDecoratorB
-(void)Operation{
    NSLog(@"具体装饰对象B的操作");
    [super Operation];
}
@end


Main方法
复制代码 代码如下:

#import <Foundation/Foundation.h>
#import "ConcreteComponent.h"
#import "ConcreteDecoratorA.h"
#import "ConcreteDecoratorB.h"

int main (int argc,const char* argv[])
{
    @autoreleasepool{
        ConcreteComponent *c = [[ConcreteComponent alloc]init];
        ConcreteDecoratorA *d1 = [[ConcreteDecoratorA alloc]init];
        ConcreteDecoratorB *d2 = [[ConcreteDecoratorB alloc]init];
        [d1 SetComponents:c];
        [d2 SetComponents:d1];
        [d2 Operation];
    }
    return 0;
}


好啦,上面是需要展示的类,语法上都很简单,没有什么需要重点说的,可能值得一提的是关于子类调用父类方法的知识点,就是在调用每个对象的Operation方法的时候,里面会有一句代码是[super Operation];这句代码构很关键,他构成了各个对象之间Operation方法的跳转,以此完成对Components类对象的”装饰”。

二、分类(Category)和委托(Delegation)
在 Object-C 里有两个种非常常见的实现模式:分类(Category)和委托(Delegation)。

1.分类 Category

分类是一种非常强大的机制,它允许你在一个已存在的类里添加新方法,而不需要去为他添加一个子类。新方法在编译的时候添加,它能像这个类的扩展方法一样正常执行。一个装饰器跟类的定义稍微有点不同的就是,因为装饰器不能被实例化,它只是一个扩展。

提示:除了你自己类的扩展,你还可在任何 Cocoa 类里的扩展添加方法。
如何使用分类:

现在你有一个 Album 对象,你需要把它显示在一个表单视图里(table view):

2016330115518863.png (310×188)

专辑的标题从哪里来?Album 只是一个模型对象,它才不会去关心你如果去显示这些数据。为了这些,你需要给 Album 类添加一些额外的代码,但是请不要直接修改这个类。

你现在就需要为 Album 添加一个分类 (category) 的扩展;它将定义一个新地方法用来返回一个数据结构,这个数据结构可以很容易的被 UITableViews 使用。

这个数据结构看起来如下:

2016330115540205.png (480×67)

为 Album 添加一个分类,导航 File\New\File… 选择 Object-C category 模版─不要习惯的去选择 Object-C class,在 Category 后面输入 TableRepresentation,Category to 后面输入 Album。

提示:你有没有注意这个新文件的名字?Album+TableRepresentation 说明它是 Album 类的一个扩展。这个习惯很重要,因为第一这很容易读,第二防止你或者其他人创建的分类跟其冲突。
打开 Album+TableRepresentation,加入下面的方法原型:

- (NSDictionary*)tr_tableRepresentation;
注意,这是一个 tr_ 开头的方法名,就像是这个分类名字的缩写一样:TableRepresentation。其次,这个习惯会避免这个方法跟其它方法重名!

提示:如果分类 (Category) 声明的一个方法跟原始类的一个方法重名,或者跟同类里的的另一个分类名字重复(或者是它的父类),当它在运行的时候,它就不知道要执行哪个方法。如果是在你自己类的分类里,它不太可能出现大的问题,但是如果一个标准 Cocoa 或者 Cocoa Touch 类里面添加这个分类的方法,就可能会引起严重的问题。
打开 Album+TableRepersentation.m 文件添加下面的方法:

复制代码 代码如下:

- (NSDictionary*)tr_TableRepersentation
{
    return @{@"titles":@[@"Artist", @"Album", @"Genre", @"Year"],
            @"values":@[self.artist, self.title, self.genre, self.year]};
};

考虑一会,为什么这种模式如些强大:

你能够直接使用 Album 的属性。
你已经添加在 Album 类里,但它并不是它的子类。如果子类需要,你同样也可以这样做。
这样一个简单的添加,Album 类的数据返回一个 UITableView 可用的数据结构,但并不需要修改 Album 的代码。
苹果在基础类里大量的使用了分类设计模式。去看看他们是怎么做的,打开 NSString.h。找到 @interface NSString,你将会看到这个类定义了三个分类:NSStringExtensionMethods, NSExtendedStringPropertyListParsing 和 NSStingDeprecated。在代码片里,分类将帮助你保持方法的组织性和分离必。

2.委托 Delegation

另外一种装饰器的设计模式是,委托 (Delegation),它是一种机制,一个对象代表另外一个对象或者其相互合作。例子,当你使用 UITableView 的时候,其中一个方法是你必需要执行的,tableView:numberOfRowsInSection:。

你可能并不期望 UITableView 知道每个 section 中有多少行,这是程序的特性。因此,计算每个 section 有多少行的工作就交给了 UITableView 的委托 (delegate)。它允许 UITableView 类不依赖它显示的数据。

当你创建了一个新的 UITableView 的时候,这里有一个类似的解释:

2016330115644873.png (480×252)

UITableView 对象的工作就是显示一个表单视图。然而,最终它都需要一些它信息,它并不拥有这些信息。然后,它会转向它的委托,发送一个添加信息的消息。在 Object-C 中实现委托模式,一个类可以通过协议 (protocol) 来声明一个可选和必选的方法。稍后,在这个教程你将覆盖一个协议 (protocols)。

它看起来比子类更容易,覆盖需要的方法,但是考虑如果是单类的话你只能创建子类。如果你想一个对象委托两个或者多个对象的时候,子类化的方法是不能实现的。

提示:这是一个很重要的模式。苹果在 UIKit 类中大量的使用了此方法:UITableView, UITextView, UITextField, UIWebView, UIAlert, UIActionSheet, UICollectionView, UIPickerView, UIGestureRecognizer, UIScrollView。这个列表还可以有很多。
如何使用委托模式:

打开 ViewController.m,在顶部引入如下文件

复制代码 代码如下:

#import "LibraryAPI.h"
#import "Album+TableRepresentation.h"



现在,在类的扩展里的添加一些私有变量,它们看起来如下:
复制代码 代码如下:

@interface ViewController (){
    UITableView *dataTable;
    NSArray *allAlbums;
    NSDictionary *currentAlbumData;
    int currentAlbumIndex;
}
@end

现在,替换类扩展里的 @interface 这一行,完成后如下:
复制代码 代码如下:

@interface ViewController () <UITableViewDataSoure, 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];
}


这里分析下上面的代码:

把背景色改为漂亮的深蓝色。
从 API 获取一个列表,它包含所有的专辑数据。不能直接使用 PersistencyManager。
创建一个 UITableView。你声明了视图控制器是 UITableView delegate/data source;因此,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: 从专辑数组中取出需要的专辑数据。当你需要显示新数据的时候,你只需要重载数据 (relaodData)。这是因为 UITableView 需要请求它的委托代理,像有多少 sections 将会在表单视图中显示,每个 section 中有多少行,每行看起来是什么样的。

在 viewDidLoad 中添加下面代码

复制代码 代码如下:

[self showDataForAlbumAtIndex:currentAlbumIndex];

当程序运行的时候它会加载当前的专辑信息。由于 currentAlbumIndex 的预设值为 0,所以会显示收藏中的第一张专辑信息。

构建并运行你的项目,你的程序会崩溃掉,在控制台会输入如下的异常:

2016330115745286.png (700×112)

出现什么问题了?你已经声明了 ViewController 中的 UItableView 的委托(delegate)和数据源(data source)。但是在这种情况下,你必需执行所有的必需方法─包含 tableView:numberOfRowsInsection:─你现在还没有它。

在 ViewContrller.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:numberOfRowsIndexSection: 返回表单视图显示的行数,匹配数据结构中标题的数目。

tableView:cellForRowAtIndexPath: 创建并返回一个带标题和信息的 cell。

现在构建并运行你的项目。你的程序开始运行并显示出下图的界面:

2016330115806312.png (320×235)

这目前为止事情看起来很不错。但是如果你回过去看第一张图片的时候,你会发现在屏幕的顶端有一个可以水平滚动的视图,用于切换专辑。它只是简单的水平滚动,为什么不做一个可以重复使用的视图来代替它呢。

    您感兴趣的教程

    在docker中安装mysql详解

    本篇文章主要介绍了在docker中安装mysql详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编...

    详解 安装 docker mysql

    win10中文输入法仅在桌面显示怎么办?

    win10中文输入法仅在桌面显示怎么办?

    win10系统使用搜狗,QQ输入法只有在显示桌面的时候才出来,在使用其他程序输入框里面却只能输入字母数字,win10中...

    win10 中文输入法

    一分钟掌握linux系统目录结构

    这篇文章主要介绍了linux系统目录结构,通过结构图和多张表格了解linux系统目录结构,感兴趣的小伙伴们可以参考一...

    结构 目录 系统 linux

    PHP程序员玩转Linux系列 Linux和Windows安装

    这篇文章主要为大家详细介绍了PHP程序员玩转Linux系列文章,Linux和Windows安装nginx教程,具有一定的参考价值,感兴趣...

    玩转 程序员 安装 系列 PHP

    win10怎么安装杜比音效Doby V4.1 win10安装杜

    第四代杜比®家庭影院®技术包含了一整套协同工作的技术,让PC 发出清晰的环绕声同时第四代杜比家庭影院技术...

    win10杜比音效

    纯CSS实现iOS风格打开关闭选择框功能

    这篇文章主要介绍了纯CSS实现iOS风格打开关闭选择框,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作...

    css ios c

    Win7如何给C盘扩容 Win7系统电脑C盘扩容的办法

    Win7如何给C盘扩容 Win7系统电脑C盘扩容的

    Win7给电脑C盘扩容的办法大家知道吗?当系统分区C盘空间不足时,就需要给它扩容了,如果不管,C盘没有足够的空间...

    Win7 C盘 扩容

    百度推广竞品词的投放策略

    SEM是基于关键词搜索的营销活动。作为推广人员,我们所做的工作,就是打理成千上万的关键词,关注它们的质量度...

    百度推广 竞品词

    Visual Studio Code(vscode) git的使用教程

    这篇文章主要介绍了详解Visual Studio Code(vscode) git的使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...

    教程 Studio Visual Code git

    七牛云储存创始人分享七牛的创立故事与

    这篇文章主要介绍了七牛云储存创始人分享七牛的创立故事与对Go语言的应用,七牛选用Go语言这门新兴的编程语言进行...

    七牛 Go语言

    Win10预览版Mobile 10547即将发布 9月19日上午

    微软副总裁Gabriel Aul的Twitter透露了 Win10 Mobile预览版10536即将发布,他表示该版本已进入内部慢速版阶段,发布时间目...

    Win10 预览版

    HTML标签meta总结,HTML5 head meta 属性整理

    移动前端开发中添加一些webkit专属的HTML5头部标签,帮助浏览器更好解析HTML代码,更好地将移动web前端页面表现出来...

    移动端html5模拟长按事件的实现方法

    这篇文章主要介绍了移动端html5模拟长按事件的实现方法的相关资料,小编觉得挺不错的,现在分享给大家,也给大家...

    移动端 html5 长按

    HTML常用meta大全(推荐)

    这篇文章主要介绍了HTML常用meta大全(推荐),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参...

    cdr怎么把图片转换成位图? cdr图片转换为位图的教程

    cdr怎么把图片转换成位图? cdr图片转换为

    cdr怎么把图片转换成位图?cdr中插入的图片想要转换成位图,该怎么转换呢?下面我们就来看看cdr图片转换为位图的...

    cdr 图片 位图

    win10系统怎么录屏?win10系统自带录屏详细教程

    win10系统怎么录屏?win10系统自带录屏详细

    当我们是使用win10系统的时候,想要录制电脑上的画面,这时候有人会想到下个第三方软件,其实可以用电脑上的自带...

    win10 系统自带录屏 详细教程

    + 更多教程 +
    Windows系统Linux系统苹果MACAndroidiOS系统鸿蒙系统