背景:去年十月入职,针对项目中存在的问题提了一系列的重构方案,到现在大概完成了百分之三四十样子,后面会针对各个问题整理一系列的笔记。首先目前的业务线是组件开发的模式,其实主站的基础组件封装的还是不错的,但目前的业务线的代码却有些问题。
问题:例如说,我们的主要model是XXXModel,该model会在各个列表中出现,feed流,个人页,推荐流,主题信息流等,以及详情页。那么现在采取的实现方式是,feed流继承baseTableVeiwVC,而其他的列表页继承自feed流(为了重用卡片的相关逻辑),然后通过重写各个小的控制方法来实现功能。类似这种VC的卡片是通过ComponentKit(下面简称CK)来实现的,详情页通过nativeView来实现。也就是维护了两套view的代码。
重构重点
- 去除不相关的VC的继承关系,从而去掉子VC中的各种重写方法,这样太不利于理解了。
- 去除详情页的nativeView代码,用一套就可以了
重构开始
抽取卡片handler类
因为该类型的卡片被各列表VC重用,所以其事件处理也肯定会被各个VC重用,那么要改变类的继承结构就首先要抽出该事件处理类,但是VC可能存在不同section的情况,所以我们抽的handler要能够用在多个section的VC中。
@protocol XXXCellHandlerDelegate <NSObject>
/// 所在VC的navigationController
- (UINavigationController *)handlerNavigationController;
/// model所在的section
- (NSUInteger)handler:(XXXCellHandler *)handler sectionForModel:(id)model;
/// model所在的viewModel
- (ListViewModel *)handler:(XXXCellHandler *)handler viewModelForModel:(id)model;
@end
@interface XXXCellHandler : NSObject
@property (nonatomic, weak) id<XXXCellHandlerDelegate> delegate;
/// 是否应该做某事
@property (nonatomic, assign) BOOL shouldDoSomeThing;
...
@end
- 该类通过delegate获取所在VC,viewModel,section等信息,来做进一步处理。
- 如果某个事件在不同VC中有分叉行为,那么建议通过property来控制,而不是通过继承来实现,不然不利于维护。当然,如果分叉行为很复杂,还是通过继承来实现比较好。
- (void)mapModel:(id)model withResult:(void(^)(ListViewModel *viewModel, NSUInteger section))resultBlock {
ListViewModel *viewModel = nil;
NSUInteger section = NSNotFound;
if ([self.delegate respondsToSelector:@selector(handler:viewModelForModel:)]) {
viewModel = [self.delegate handler:self viewModelForModel:model];
}
if ([self.delegate respondsToSelector:@selector(handler:sectionForModel:)]) {
section = [self.delegate handler:self sectionForModel:model];
}
resultBlock(viewModel, section);
}
- 通过map方法来映射点击事件的model所对应的section和viewModel,来支持多个section的VC
抽取上边的handler后,如果一个VC要构造新的列表那么只需要接入handler,然后实现delegate就可以完成了。
抽取卡片相关的其他功能
和handler类似,卡片相关的功能还有其他,例如视频自动播放,各个VC都会存在的,那么这时候抽取一个manager就很有必要了
更改VC继承结构
首先从子类开始,因为feedVC太庞大了,不能动,所以只能从比较小的子VC开始。
重构前的结构大概是这样子的:
BaseTableVC (tableView的基础VC,包含上拉下拉加载等逻辑)
|
FeedVC(这个就比较大了,包含卡片相关的所有逻辑,包含打点,视频相关,已读逻辑等等等)
| | |
OtherVC1 otherVC2 otherVC3 ...
这种结构决定了几个比较大的缺陷:
1.FeedVC要包含绝大多数逻辑
2.FeedVC要分出很多小的方法,来进行逻辑判断,而每个子VC都要都要重写很多方法,为了去掉不想要的逻辑会有很多空方法
3.FeedVC会很大
4.扩展受限,FeedVC添加功能,还要改子类
5.一无是处...
重构后,VC只需要持有handler,并实现其delegate就可以,大致结构是这样:
cellHandler(用来处理卡片的事件,VC持有并实现delegate就可以,支持多section的tableVC)
videoManager(自动播放的管理类,VC持有并实现delegate就可接入)
VC的结构就可以调整成不相互影响的结构了:
BaseTableVC (tableView的基础VC,包含上拉下拉加载等逻辑)
| | | |
FeedVC OtherVC1 otherVC2 otherVC3 ...
这样调整的好处:
1.VC间互不影响
2.方便接入卡片的相关逻辑,想接哪个就引入就可以了
3.方便控制逻辑,通过property去控制业务走向
4.没有了那么多不相关的逻辑,主线清晰明了
重构详情页
调整前结构:
首先我们的卡片逻辑很多,结合起来比较复杂,要实现还是比较麻烦的。
而普通的卡片列表使用CK来实现的,也就是我们有一套CK的view。
但详情页使用native实现的,也就是我们有两套view,但表现是一致的。
normalTableView ------> ComponentKit‘s card
detailVC ------> native's card
那么结果就是维护麻烦,工程量翻倍,什么东西都是两份(delegate, event, view ... )。
调整后的结构:
把详情页看成一个tableVC
BaseTableVC (tableView的基础VC,包含上拉下拉加载等逻辑)
| | | | |
detailVC FeedVC OtherVC1 otherVC2 otherVC3 ...
一样的配方,不一样的味道,世界都清净了,完美套用
总结
通过这部分代码的重构总结了一些编码上的规范:
- 明确各角色的功能,尤其是重用度较高的时候,不要把所有功能都写在VC里,不要采取继承+重写的方式来实现,这样真的很难读懂,因为没有明确暴漏业务上的逻辑。
- 组合优于继承,继承用的过多会造成类过多,更不利于维护,所以当我们实现功能的时候要优先考虑组合。
- 明确各个角色的类型,例如viewModel负责object的维护和网络加载等,VC更注重于角色间的协调及事件处理,那么如果卡片复用度较高,则可以抽取和卡片对应的handler,来处理卡片的事件,方便服用。