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 复杂的列表适配器层级分析
复杂列表适配器分为三个层
- Cell适配器层
- Section适配器层
- 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中的转发来减少代码量,使用的的方法为respondsToSelector
和forwardingTargetForSelector
,具体可以看下面的代码。
// 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这里下载到
- iOS模式分析-使用适配器模式重构TableView
- 代码重构-笔记-适配器模式?
- iOS设计模式-适配器模式
- iOS 设计模式 - 适配器模式
- IOS适配器模式
- iOS设计模式-适配器
- 【iOS设计模式】---- 适配器模式,观察者模式
- 适配器模式具体使用
- 适配器模式(ADAPTER)案例分析
- iOS适配器(Adapter)模式
- 【iOS开发】适配器模式与观察者模式
- iOS设计模式浅析之适配器模式
- 适配器模式,浅谈什么是适配器模式,简单分析
- 适配器模式的简单使用
- 重学java23种设计模式(6)适配器模式
- 实际使用 工厂模式+适配器模式
- IOS设计模式之三(适配器模式,观察者模式)
- iOS设计模式之三(适配器模式,观察者模式)
- iOS逆向之五-MACH-O文件解析
- mysql5.6基于schema的同步的问题总结
- IOS自动进行View标记
- 微信小程序手记
- cocos2dx clippingNode裁剪
- iOS模式分析-使用适配器模式重构TableView
- 设计模式简介
- test
- iOS使用UITableView实现的富文本编辑器
- SRS-DOLPHIN
- TagView引用笔记
- iOS使用Instrument-Time Profiler工具分析和优化性能问题
- Java is-a、has-a和like-a、组合、聚合和继承 两组概念的区别
- [Python]网络爬虫(三):使用cookiejar管理cookie 以及 模拟登录知乎