iOS模式分析-使用适配器模式重构TableView

来源:互联网 发布:java web发布到服务器 编辑:程序博客网 时间:2024/06/07 13:14

本文介绍了适配器模式的定义和概念,以及实际开发中的场景和案例,对应的代码可以在AdapterPatternDemo这里下载到。

1. 定义

什么是适配器模式?(电源适配器、转接头)
特点:

  • 将一个原始接口转成客户端需要的接口
  • 原始接口不兼容现在新的接口,将它们两个接口一起工作需要适配器解决

2. 应用场景

  • 接口不兼容(适配器模式)
  • 可以重复使用的类,用于与一些彼此之间没有太大关联的一些类一起工作(适配器模式)
  • 统一输出接口,输入的类型无法确定(适配器模式)

3. 角色划分

  • 第一个角色:适配器(最核心关键角色)
  • 第二个角色:目标接口
  • 第三个角色:被适配类(角色)
  • 第四个角色:客户端

4. 案例

4.1 案例说明

将美元(被适配对象—>Adaptee)——>人民币(目标接口——>Target)
适配器将美元转成人民币(Adaper)

4.2 定义Target协议

#import <Foundation/Foundation.h>@protocol TargetProtocal <NSObject>- (NSInteger)getCNY;@end

4.3 定义被适配的对象类Adaptee

// USDAdaptee.h#import <Foundation/Foundation.h>@interface USDAdaptee : NSObject- (void)setUSD:(NSInteger)usd;- (NSInteger)getUSD;@end// USDAdaptee.m#import "USDAdaptee.h"@interface USDAdaptee ()@property (nonatomic, assign) NSInteger usd;@end@implementation USDAdaptee- (void)setUSD:(NSInteger)usd {    _usd = usd;}- (NSInteger)getUSD {    return _usd;}@end

4.4 使用对象适配器

对象适配器使用的是组合的模式,适配器类实现Target协议,并且在适配器类中定义一个Adaptee的属性,重写协议返回对Adaptee属性对象做相应的转换操作,来达到让Adaptee适配Target的目的。

// CNYObjectAdapter.h#import <Foundation/Foundation.h>#import "USDAdaptee.h"#import "TargetProtocal.h"@interface CNYObjectAdapter : NSObject <TargetProtocal>@property (nonatomic, strong) USDAdaptee* usd;- (instancetype)initWithUSD:(USDAdaptee*)usd;@end// CNYObjectAdapter.m#import "CNYObjectAdapter.h"@implementation CNYObjectAdapter- (instancetype)initWithUSD:(USDAdaptee*)usd {    self = [super init];    if (self) {        _usd = usd;    }    return self;}- (NSInteger)getCNY {    return _usd.getUSD * 6.8;}@end

4.5 使用类适配器

类适配器使用的是继承的模式,适配器类实现Target协议,并且该适配器类继承Adaptee类,在重写的Target协议方法中使用super调用Adaptee的方法做相应的转换操作,来达到让Adaptee适配Target的目的。

// CNYClassAdapter.h#import <Foundation/Foundation.h>#import "USDAdaptee.h"#import "TargetProtocal.h"@interface CNYClassAdapter : USDAdaptee <TargetProtocal>@end// CNYClassAdapter.m#import "CNYClassAdapter.h"@implementation CNYClassAdapter- (NSInteger)getCNY {    return [self getUSD] * 6.8;}@end

4.6 客户端使用

    // 类适配器的使用    CNYClassAdapter* cnyClsAdapter = [CNYClassAdapter new];    [cnyClsAdapter setUSD:1000];    NSInteger cny = [cnyClsAdapter getCNY];    NSLog(@"CNYClassAdapter ==> cny = %@", @(cny));        // 对象适配器的使用    USDAdaptee* usd = [USDAdaptee new];    [usd setUSD:1000];    CNYObjectAdapter* cnyObjAdapter = [[CNYObjectAdapter alloc] initWithUSD:usd];    NSInteger cny2 = [cnyObjAdapter getCNY];    NSLog(@"CNYClassAdapter ==> cny = %@", @(cny2));    // 结果输出    //  CNYClassAdapter ==> cny = 6800    // CNYClassAdapter ==> cny = 6800    

5. 真实开发案例

真实的开发案例中,会从一般的开发步骤说起,首先对一般的场景代码进行重构,把列表UITableVIewDataSource/UITableViewDelegate独立为一个简单的适配器;然后在此基础上进行抽象和扩展,构建一个更加灵活和易扩展的复杂列表适配器。

5.1 一般的开发步骤

一般的,在做一个列表的展示数据的时候,我们习惯的把创建列表、创建列表数据、注册列表的cell、实现列表的UITableVIewDataSource/UITableViewDelegate方法等步骤放在ViewController来完成,类似如下的代码,这种方法在简单的列表中看不出弊端,如果这个ViewController中添加了其他更多的逻辑、代码、这个类就显得十分庞大了;此外其他的页面有用到这个一样的列表只是数据不同,那么还得重新去实现这个步骤,列表的这部分逻辑不能够复用,产生了冗余的代码,don't repeat yourself,这很糟糕,所以很有必要重构这段代码,让他能够很好的进行复用,使用适配器模式就能够很好的做到这点。下面会讲到一个简单点的适配器和一个复杂点的适配器,来重构我们的代码。

@implementation SimpleViewController- (void)viewDidLoad {    // 处理UI和数据的步骤省略....      // retister cell    [self.tableView registerClass:[GameCell class] forCellReuseIdentifier:NSStringFromClass([GameCell class])];    }#pragma mark - ......::::::: UITableViewDelegate :::::::......- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {    return _datas.count;}- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {    GameCell* cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([GameCell class])];    GameModel* gameModel = _datas[indexPath.row];    [cell loadData:gameModel indexPath:indexPath];    return cell;}- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {    [tableView deselectRowAtIndexPath:indexPath animated:YES];        UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];}@end

5.2 简单的列表适配器

简单的列表适配器就是把和UITableVIewDataSource/UITableViewDelegate相关的代码独立出来,来减轻ViewController,实现起来也很简单:

// SimpleAdapter.h#import <Foundation/Foundation.h>#import <UIKit/UIKit.h>@interface SimpleAdapter : NSObject <UITableViewDelegate, UITableViewDataSource>- (instancetype)initWithTableView:(UITableView*)tableView datas:(NSMutableArray*)datas;@end// SimpleAdapter.m#import "SimpleAdapter.h"#import "GameCell.h"#import "GameModel.h"@interface SimpleAdapter ()@property (nonatomic, strong) NSMutableArray* datas;@end@implementation SimpleAdapter- (instancetype)initWithTableView:(UITableView*)tableView datas:(NSMutableArray*)datas {    self = [super init];    if (self) {        // retister cell        [tableView registerClass:[GameCell class] forCellReuseIdentifier:NSStringFromClass([GameCell class])];        _datas = datas;    }    return self;}- (void)dealloc {    NSLog(@"=====SimpleAdapter dealloc=====");}#pragma mark - ......::::::: UITableViewDelegate :::::::......- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {    return _datas.count;}- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {    GameCell* cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([GameCell class])];    GameModel* gameModel = _datas[indexPath.row];    [cell loadData:gameModel indexPath:indexPath];    return cell;}- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {    [tableView deselectRowAtIndexPath:indexPath animated:YES];        UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];}@end

5.3 简单列表适配器的使用

简单列表适配器的使用十分的简单,把TableVIew的delegate和DataSource设置为Adapter对象就行了,并且列表的适配器复用也变得十分的方便了,当然使用这种方法在一些复杂的场景就不那么适用了,下面会在简单列表适配器的基础上进行扩展,也就是复杂的列表适配器,适用于更复杂的更一般性的场景,并且具有良好的扩张性。

@implementation SimpleAdapterViewController- (void)viewDidLoad {    // 处理UI和数据的步骤省略....        self.adapter = [[SimpleAdapter alloc] initWithTableView:self.tableView datas:datas];    self.tableView.dataSource = self.adapter;    self.tableView.delegate = self.adapter;}@end

5.4 复杂的列表适配器

5.4.1 复杂的列表适配器层级分析

复杂列表适配器分为三个层

  1. Cell适配器层
  2. Section适配器层
  3. List列表适配器层

这三个层可以是独立的存在,同时地底层也可以作为高层的依赖,使用组合的方式实现扩展。
对应的创建三个层的协议:

// Cell适配器层->CellAdapterProtocal@protocol CellAdapterProtocal <NSObject, UITableViewDelegate, UITableViewDataSource>- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;@end// Section适配器层->SectionAdapterProtocal@protocol SectionAdapterProtocal <NSObject, UITableViewDelegate, UITableViewDataSource>- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section;- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section;@end// List列表适配器层->ListAdapterProtocal@protocol ListAdapterProtocal <NSObject, SectionAdapterProtocal, CellAdapterProtocal>- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;@end
5.4.2 对应层的Base实现

对应层需要做一个Base的实现,目的是为了把通用的工作放在Base实现类中,把差异的工作放到子类中,这样达到复用代码和减少实现代码的目的。

  • Cell适配器层的Base实现
@implementation BaseCellAdapter- (instancetype)initWithTableView:(UITableView*)tableView datas:(NSMutableArray*)datas {    self = [super init];    if (self) {        // retister cell        for (Class claz in [self registerCellClasses]) {            [tableView registerClass:claz forCellReuseIdentifier:NSStringFromClass(claz)];        }        _datas = datas;    }    return self;}#pragma mark - ......::::::: UITableViewDelegate :::::::......- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {    return self.datas.count;}#pragma mark - ......::::::: override :::::::......- (NSArray*)registerCellClasses {    return nil;}@end
  • Section适配器层的Base实现

Section适配器层除了做了和列表Section展示有关的工作,还负责做Cell展示相关的工作,所以Section适配器层依赖了Cell适配器层,需要把Cell适配器层用到的内容转发给Cell适配器层,转发这部分使用了OC中的转发来减少代码量,使用的的方法为respondsToSelectorforwardingTargetForSelector,具体可以看下面的代码。

// BaseSectionAdapter.h@interface BaseSectionAdapter : NSObject <SectionAdapterProtocal, CellAdapterProtocal>@property (nonatomic, strong) NSString* sectionTitle;@property (nonatomic, assign) NSInteger sectionHeight;@property (nonatomic, strong) id<CellAdapterProtocal> cellAdapter;- (instancetype)initWithCellAdapter:(id<CellAdapterProtocal>)cellAdapter sectionTitle:(NSString*)sectionTitle sectionHeight:(NSInteger)sectionHeight;@end// BaseSectionAdapter.m@implementation BaseSectionAdapter- (instancetype)initWithCellAdapter:(id<CellAdapterProtocal>)cellAdapter sectionTitle:(NSString*)sectionTitle sectionHeight:(NSInteger)sectionHeight {    self = [super init];    if (self) {        _cellAdapter = cellAdapter;        _sectionTitle = sectionTitle;        _sectionHeight = sectionHeight;    }    return self;}#pragma mark - ......::::::: Section Handler :::::::......- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {    return self.sectionHeight;}- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {    return self.sectionTitle;}- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {    return nil;}- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {    return 0;}- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {    return nil;}#pragma mark - ......::::::: Cell Handler :::::::......#pragma mark 使用事件转发处理Cell- (BOOL)respondsToSelector:(SEL)aSelector{    BOOL responds = [super respondsToSelector:aSelector];    if (!responds && self.cellAdapter != self) {        responds = [self.cellAdapter respondsToSelector:aSelector];    }    return responds;}- (id)forwardingTargetForSelector:(SEL)aSelector {    if ([self.cellAdapter respondsToSelector:aSelector]) {        return self.cellAdapter;    }    return self;}@end
  • List列表适配器层的Base实现

List列表适配器层主要是做一个整合的实现,需要把对应的事件转发给对应的Section适配器层,Section适配器层再负责把对应的事件转发给Cell适配器层。

// BaseListAdapter.h@interface BaseListAdapter : NSObject <ListAdapterProtocal>@property (nonatomic, strong) NSMutableArray<id<SectionAdapterProtocal>>* sections;@end// BaseListAdapter.m@implementation BaseListAdapter #pragma mark - ......::::::: Section Handler :::::::......- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {    return self.sections.count;}#pragma mark - ......::::::: Section Handler :::::::......- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {    id<SectionAdapterProtocal> sectionAdapter = self.sections[section];    return [sectionAdapter tableView:tableView heightForHeaderInSection:section];}- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {    id<SectionAdapterProtocal> sectionAdapter = self.sections[section];    return [sectionAdapter tableView:tableView titleForHeaderInSection:section];}- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {    id<SectionAdapterProtocal> sectionAdapter = self.sections[section];    return [sectionAdapter tableView:tableView viewForHeaderInSection:section];}- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {    id<SectionAdapterProtocal> sectionAdapter = self.sections[section];    return [sectionAdapter tableView:tableView heightForFooterInSection:section];}- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {    id<SectionAdapterProtocal> sectionAdapter = self.sections[section];    return [sectionAdapter tableView:tableView viewForFooterInSection:section];}#pragma mark - ......::::::: Cell Handler :::::::......- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {    id<SectionAdapterProtocal> cellAdapter = self.sections[section];    return [cellAdapter tableView:tableView numberOfRowsInSection:section];}- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {    id<SectionAdapterProtocal> cellAdapter = self.sections[indexPath.section];    return [cellAdapter tableView:tableView cellForRowAtIndexPath:indexPath];}- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {    id<SectionAdapterProtocal> cellAdapter = self.sections[indexPath.section];    return [cellAdapter tableView:tableView didSelectRowAtIndexPath:indexPath];}@end
5.4.3 具体的适配器
  • 具体的Cell适配器
@implementation GameCellAdapter#pragma mark - ......::::::: UITableViewDelegate :::::::......- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {    GameCell* cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([GameCell class])];    GameModel* gameModel = self.datas[indexPath.row];    [cell loadData:gameModel indexPath:indexPath];    return cell;}- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {    [tableView deselectRowAtIndexPath:indexPath animated:YES];        UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];}#pragma mark - ......::::::: override :::::::......- (NSArray*)registerCellClasses {    return @[[GameCell class]];}@end
  • 具体的Section适配器

简单的定义了一个GameSectionAdapter继承了BaseSectionAdapter,没有更多的扩展,实际情况可以在这边添加更多自定义功能的代码,来扩展Section的功能。

@interface GameSectionAdapter : BaseSectionAdapter@end@implementation GameSectionAdapter@end
  • 具体的List适配器
    和GameSectionAdapter一样,List适配器一般是比较固定的,一般的可以直接使用BaseListAdapter来完成对应的工作,无需额外的工作,这里作为Demo还是简单的继承BaseListAdapter实现一个自定义的List适配器,这里可以直接使用BaseListAdapter达到相同的效果。
@interface GameDetailListAdapter : BaseListAdapter@end@implementation GameDetailListAdapter@end
5.4.4 复杂适配器的使用

复杂适配器需要创建CellAdapter对象、SectionAdapter对象、ListAdapter对象,进行组合,最后用ListAdapter对象作为列表的delegate和dataSource。

@implementation SectionAdapterViewController- (void)viewDidLoad {    [super viewDidLoad];    // 处理UI和数据的步骤省略....        GameCellAdapter* cellAdapter = [[GameCellAdapter alloc] initWithTableView:self.tableView datas:datas];    GameSectionAdapter* fpsSectionAdapter = [[GameSectionAdapter alloc] initWithCellAdapter:cellAdapter sectionTitle:@"FPS Games" sectionHeight:60];    GameSectionAdapter* roleSectionAdapter = [[GameSectionAdapter alloc] initWithCellAdapter:cellAdapter sectionTitle:@"Role Play Games" sectionHeight:60];    self.adapter = [[GameDetailListAdapter alloc] init];    self.adapter.sections = [@[fpsSectionAdapter, roleSectionAdapter] mutableCopy];        self.tableView.dataSource = self.adapter;    self.tableView.delegate = self.adapter;}@end

实现的效果如下图:
复杂适配器的使用效果图

6. UML分析

对象适配器和类适配器

7. Demo

本文的Demo可以在AdapterPatternDemo这里下载到

原创粉丝点击