首页 > 代码库 > 装饰设计模式

装饰设计模式

装饰设计模式

装饰设计模式动态的添加一些行为和任务到一个对象中且不需要去修改它的代码。当然你也可以选择用继承的方式-通过包装成另一个对象去改变它的行为。

在objective-c中由两个非常常用的实现方式:分类和代理。(Category, Delegate)

Category

分类是一种非常有用的机制,它允许你去添加一些方法到已经存在的类中且不用去继承它。这些新方法会在编译的时候添加上去,且可以像这个被扩展的类中的其他方法一样被执行。它和典型的装饰设计模式由一点轻微的不同,因为它并不hold住它所扩展的类的实例。

           小贴士:除了向你自己的类去添加方法,你还可以向cocoa中的类去添加方法。

如何使用分类

想像这是你想要在tableView中显示album数据的一种方案。

 

这些album titles从哪里来?Album是一个模型对象,所以它并不在意你是怎么样显示数据的,你将要需要一些额外的代码去将这些功能添加到Album类中去,但是不要直接去修改这些类。

你将要创建一个分类去扩展Album。它要定义一个新的方法去返回一个数据结构来被UITableViews轻松的使用。这些数据结构看起来应该是这样的。


为了添加一个分类到Album中,新建一个文件选择Objective-C category 模版,在category field中键入TableRepresentation,在Category On上写入Album

Album+TableRepresentation.h加入以下方法的声明。


- (NSDictionary *)tr_tableRepresentation;


注意到这里有一个tr_方法名的前面,就像一个分类的名字的前缀。这样的命名约定有利于防止和其他方法冲突。

   注意:如果在分类中声明的方法名字和在原来的类中的方法名字一样,或者和另一个类扩展中的方法名字一样,那么就会显示未定义,因为这些方法的实现是在运行时,如果你用分类去扩展你自己定义的类,那么出现问题的概率不大,但是如果你用分类添加方法到Cocoa或者CocoaTouch的类中,那有可能会产生很严重的问题。

 

Album+TableRepresentation.m添加下面的方法

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

想一下这个设计模式有多强大:

1你可以直接使用Album中的属性。

2 你可以不继承就可以向一个类添加方法。当然如果你想继承的话,也可以继承。

3 它可以简单的让你返回一个UITableView-ish的Album的显示类型,而不用修改Album的代码。

苹果公司使用分类在很多在基础的类上。要想去看他们是怎样实现的,可以打开NSString.h,找到@interface NSString ,那你将会看到这个类的定义和一下三个分类紧紧联系在一起:

NSStirngExtensionMethods, NSExtendedStringPropertyListParsing,NSStringDeprecated。分类把这些方法有序的组织起来而又分成几部分。

 

代理(delegateon)

另一个装饰设计模式就是代理,就是一个对象可以代表或者协助另一个对象的一种机制。例如,当你使用UITableView,其中你必须实现的方法就是tableView:number numberOfRowsInSection.

你不能指望UITableView去知道你想在每一个分区里面有多少行。因此,计算每个分区有多少行的任务就交给了UITableView的代理。这让UITableView可以和它要显示的数据进行独立

UITableView 对象的工作是显示一个table view,然后最终它还是需要一些它根本没有拥有的数据。那么,它会向它的代理去发送一条消息去请求一些额外的信息。在Objective-C中的代理设计模式的实现中,一个类可以通过协议protocol声明必须的required或者可选的optional的方法。你将会实现这些协议在这个教程的后半部分。

似乎看起来通过继承一个对象然后去重写它的需要的方法更容易,凡事考虑到你你只能继承一个类。如果你向一个对象成为两个或者更多对象的代理,那你不能通过继承去实现这个目标。

   小贴士:这是一个很重要的模式,苹果公司使用这个方法在大部分的UIKit的类中:UITableView,UITextView,UITextField,UIWebView,UIAlert,UIAction,UICollectionView,UIPickerView,UIGestureRecognizer,UIScrollerView等等。

如何使用代理模式:

在ViewController.m中加入导入这些文件的头文件。

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

利用类扩展去添加这些私有变量。

@interfaceViewController ()  
   
{  
   
    UITableView *dataTable;  
   
    NSArray *allAlbums;  
   
    NSDictionary*albumData;  
   
    intcurrentAlbumIndex;  
   
}  
   
@end

然后在@interfaceViewController ()加上   <UITableViewDataSource,UITableViewDelegate>

这是你怎样使你的代理遵从一个协议-想像它是一个被代理去完成方法的协议的一个约定,在这里你让ViewController去遵守UITableViewDataSourceUITableViewDelegate协议,这个方法使得UITableView可以绝对保证这些必须方法会被它的代理所实现。

接下来,在viewDidLoad中加入这些代码:

self.view.backgroundColor = [UIColorcolorWithRed:0.76f green:0.81f blue:0.87f alpha:1];  
   
    currentAlbumIndex = 0;  
   
    allAlbums = [[LibraryAPIsharedInstance]getAlbums];  
   
    dataTable = [[UITableViewalloc]initWithFrame:CGRectMake(0, 120, self.view.frame.size.width, self.view.frame.size.height-120) style:UITableViewStyleGrouped];  
   
    dataTable.delegate = self;  
   
    dataTable.dataSource = self;  
   
    dataTable.backgroundColor = nil;  
   
[self.viewaddSubview:dataTable];

这里是这些代码的讲解:

       1首先将背景颜色改成了一个相对友好的背景色。

       2通过API而不是PersistencyManager得到了albums的列表。

3在这里创建了UITableView,你声明了这个控制器是UITableViewdelegate/dataSource,因此UITableView的所有必须的方法都会由控制器提供。

然后添加这个方法在控制器实现代码中:

- (void)showDataForAlbumAtIndex:(int)index  
   
{  
   
    if (index < [allAlbumscount]) {  
   
        Album *album = [allAlbumsobjectAtIndex:index];  
   
       albumData = [album tr_tableRepresentation];  
   
    }else{  
   
        albumData = nil;  
   
    }     
   
    [dataTablereloadData];  
   
}




showDataForAlbumAtIndex:从albums这个数组中获取了所需答album的数据。然后你之需要去调用reloadData.这会让UITableView询问它的代理诸如在table view的每个分区中显示多少行,多少个分区,每一行应该怎么样之类的事情。

viewDidLoad的结尾处加上[self showDataForAlbumAtIndex:currentAlbumIndex];

这会在应用启动的时候load现在的album,然后因为currentAlbumIndex原先被初始化为0,所以只是在会显示第一个album

编译运行你的工程,你会遇到一个崩溃伴随一个异常的显示在调试控制台。

这怎么了?因为你声明了控制器成为UITableViewdelegatedataSource,但是这样的话你必须遵守去实现它的必须的方法包括numberOfRowsInSection这个你还没实现的方法。向ViewController.m中加入这两个方法。

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section  
   
{  
   
    return [albumData[@"title"] count];  
   
}  
   
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  
   
{  
   
    UITableViewCell *cell =[tableView dequeueReusableCellWithIdentifier:@"Album"];  
   
    if (!cell) {  
   
        cell = [[UITableViewCellalloc]initWithStyle:UITableViewCellStyleValue1reuseIdentifier:@"Album"];  
   
    }  
   
    cell.textLabel.text = [albumData [@"title"] objectAtIndex:indexPath.row];  
   
    cell.detailTextLabel.text = [albumData[@"values"]objectAtIndex:indexPath.row];  
   
    return cell;  
   
}




前一个方法返回在table view中要显示的行数,在这里和数据结构中的titles的数量相同。后者创建并返回了一个带有titlevaluecell

编译和运行工程,你的应用应该是向这样展示在你的面前。


到目前为止,事情好像看起老很棒,但是你如果你调用第一张图片去展示启动后的app,那将会由一个水平的滚动条在屏幕的albums转换之间的顶部。与其创建一个单一用途的水平滚动条,为什么不使它成为一个通用的视图。

为了是这个视图可重用,所有的关于它的内容的决定都该留给另一个对象-它的代理。这个horizontal scroller应该定义一些方法让它的代理去实现以至于去和scroller一起工作,和UITableViewde的代理方法想类似。我们将会在下一个设计模式中去讨论这个设计模式。


装饰设计模式