去model化开发
前言
去model化是一种框架设计上的做法,其中的model并不是指架构中的model层,套用Casa大神博客中的原文就是:
model化就是使用数据对象,去model化就是不使用数据对象。
常见的去model化做法是使用字典保存数据信息,然后提供一个reformer负责将这些字典数据转换成View层可展示的信息,其流程图如下:
更详细的理论知识可以看Casa大神的去model化和数据对象(http://casatwy.com/OOP_nomodel.html)。本文基于Casa大神的实践基础使用另外一种去model化的实现方式。
使用背景
在很早之前就看过大神的文章,不过一直没有去尝试这种做法。在笔者最近跳入新坑之后,总算是有了这么一次机会。需求是存在着三个非常相似的cell,但分别对应着不同的数据model:
总结三个cell都需要的展示数据包括:
产品名称
使用条件
截止日期
背景图片
此外,优惠信息属于第一个和第二个独有的。现在这一需求存在的问题主要有这么三点:
三种数据对象在服务器返回的属性字段中命名差别大
这是大部分的应用都存在的一个问题,但是本文中的数据对象有一个显著的特点是它们对应显示的cell
存在很大的相似度,可以被转换成相似的展示数据三种
cell
可以封装成一种,却分别对应着不同的数据对象
这里涉及cell
和数据对象的对接问题,如果cell
在以后发生改变了,那么原有的数据对象是否还能适用控制器需要在数据源方法中调配不同的
cell
和model
,耦合过大
这个也是常见的问题之一,通常可以考虑适用工厂模式将调配的业务分离出去,但在本文中采用去model
的方式实现
这些问题都有可能导致项目后期维护的过程中变得难以修改,小小的需求改动都会导致代码的大改。笔者的解决方式是制定cell
和model
之间对应的两个协议,从而控制器无需理会两者的具体类型。
实现
我在上一篇文章MVC架构杂谈中提到过M
层的业务逻辑放在model
中,虽然本文要去model化
,但只是去除属性对象,自身的逻辑处理还保留着。下面是笔者去model化
的协议图以及协议声明属性:
@protocol LXDTicketModelProtocol
@optional
@property (nonatomic, readonly) NSAttributedString * perferential;
@required
@property (nonatomic, readonly) NSString * backgroundImageName;
@property (nonatomic, readonly) NSString * goodName;
@property (nonatomic, readonly) NSString * effectCondition;
@property (nonatomic, readonly) NSString * deadline;
@property (nonatomic, readonly) LXDCellType type;
- (instancetype)initWithDict: (NSDictionary *)dict;
@end
@protocol KMCTicketCellProtocol
- (void)configurateCellWithModel: (id)model;
@end
对于本文之中这种存在共同显示效果的model,可以声明一个包含多个readonly属性的协议,让这些模型对象在协议的getter方法中执行数据->展示这一过程的业务逻辑,而model自身只需简单的持有字典数据即可:
以LXDCouponTicketModel为例,协议的实现代码如下:
// h文件
@interface LXDCouponTicketModel: NSObject
@end
// m实现
@implementation LXDCouponTicketModel
{
NSDictionary * _dict;
}
- (NSString *)backgroundImageName
{
return ([_dict[@"overdue"] boolValue] ? @"coupon_overdue" : @"coupon_common");
}
- (NSAttributedString *)perferential
{
NSAttributedString * result = objc_getAssociatedObject(self, KMCPerferentialKey);
if (result) { return result; }
NSMutableAttributedString * attributedString = [[NSMutableAttributedString alloc] initWithString: @"¥" attributes: @{ NSFontAttributeName: [UIFont systemFontOfSize: 16] }];
[attributedString appendAttributedString: [[NSAttributedString alloc] initWithString: [NSString stringWithFormat: @"%g", [_dict[@"ticketMoney"] doubleValue]] attributes: @{ NSFontAttributeName: [UIFont boldSystemFontOfSize: 32] }]];
[attributedString addAttributes: @{ NSForegroundColorAttributeName: KMCCommonColor } range: NSMakeRange(0, attributedString.length)];
result = attributedString.copy;
objc_setAssociatedObject(self, KMCPerferentialKey, result, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return result;
}
- (NSString *)goodName
{
return [_dict[@"goodName"] stringValue];
}
- (NSString *)effectCondition
{
return [NSString stringWithFormat: @"· 满%lu元可用", [_dict[@"minLimitMoney"] unsignedIntegerValue]];;
}
- (NSString *)deadline
{
return [NSString stringWithFormat: @"· 兑换截止日期:%@", _dict[@"deadline"]];
}
- (LXDCellType)type
{
return LXDCellTypeCoupon;
}
- (instancetype)initWithDict: (NSDictionary *)dict
{
if (self = [super init]) {
_dict = dict;
}
return self;
}
通过让三个数据对象实现这个协议,笔者将要展示的数据结果进行统一。在这种情况下,封装成单个的cell也无需关心model的具体类型是什么,只需实现针对单元格配置的协议方法获取展示的数据即可:
// h文件
@interface LXDTicketCell: UITableViewCell
@end
// m实现
#define LXDCommonColor [UIColor colorWithRed: 253/255. green: 99/255. blue: 99/255. alpha: 1]
@implementation LXDTicketCell
- (void)configurateWithModel: (id)model
{
UIView * goodInfoView = _goodNameLabel.superview;
if ([model type] != KMCTicketTypeConvert) {
[goodInfoView mas_updateConstraints: ^(MASConstraintMaker *make) {
make.left.equalTo(_perferentialLabel.mas_right).offset(10); }];
} else {
[goodInfoView mas_updateConstraints: ^(MASConstraintMaker *make) {
make.left.equalTo(_backgroundImageView.mas_left).offset(18); }];
}
[_use setTitleColor: LXDCommonColor forState: UIControlStateNormal];
_backgroundImageView.image = [UIImage imageNamed: [model backgroundImageName]];
_perferentialLabel.attributedText = [model perferential];
_effectConditionLabel.text = [model effectCondition];
_goodNameLabel.text = [model goodName];
_deadlineLabel.text = [model deadline];
[_effectConditionLabel sizeToFit];
[_goodNameLabel sizeToFit];
[_deadlineLabel sizeToFit];
}
@end
三个问题前两个已经解决了:通过协议统一数据对象的展示效果,这时候并不需要model保存多个属性对象,只需要在适当的时候直接从字典中获取数据并执行数据可视化这一逻辑即可。cell也不会受限于传入的参数类型,只需要简单的调用协议方法获取需要的数据即可。那么最后一个控制器的协调问题就变得简单了:
// m实现
@interface LXDTicketViewController ()
@property (nonatomic, strong) NSMutableArray > * couponTickets;
@property (nonatomic, strong) NSMutableArray > * discountTickets;
@property (nonatomic, strong) NSMutableArray > * convertTickets;
@end
@implementation LXDTicketViewController
#pragma mark - UITableViewDataSource
- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: KMCTicketCommonCellIdentifier];
if ([cell conformsToProtocol: @protocol(LXDTicketCellProtocol)]) {
[(id)cell configurateCellWithModel: [self modelWithIndexPath: indexPath]];
}
return cell;
}
#pragma mark - Data Generator
- (id)modelWithIndexPath: (NSIndexPath *)indexPath
{
return self.currentModelSet[indexPath.row];
}
- (NSMutableArray > *)currentModelSet
{
switch (_ticketType) {
case KMCTicketTypeCoupon:
return _couponTickets;
case KMCTicketTypeDiscount:
return _discountTickets;
case KMCTicketTypeConvert:
return _convertTickets;
}
}
@end
当cell和model共同通过协议的方式实现交流的时候,控制器存储的数据源也就可以不关心这些对象的具体类型了。通过泛型声明多个数据源,控制器此时的职责仅仅是根据状态机的改变决定使用哪个数据源来展示而已。当然,虽然笔者统一了这三个数据源的类型,但是归根到底总要根据服务器返回的json创建不同的数据对象存放到这些数据源中。如果把这个业务放在控制器中原本就达不到松耦合的作用,因此引入一个中间人Helper来完成这个业务:
// h文件
@interface LXDTicketDataHelper: NSObject
+ (void)anaylseJSON: (NSString *)JSON complete: (void(^)(NSMutableArray > *)models);
@end
// m实现
#import "LXDCouponTicketModel.h"
#import "LXDConvertTicketModel.h"
#import "LXDDiscountTicketModel.h"
@implementation LXDTicketDataHelper
+ (void)anaylseJSON: (NSString *)JSON complete: (void(^)(NSMutableArray > *)models)
{
NSParameterAssert(JSON);
NSParameterAssert(complete);
[LXDQueue executeInGlobalQueue: ^{
Class ModelCls = NULL;
NSDictionary * jsonDict = [NSDictionary dictionaryWithJSON: JSON];
NSMutableArray > * results = @[].mutableCopy;
// 使用switch简单工厂,如果case太多时,使用继承关系的工厂会更好
switch ((LXDModelType)[jsonDict[@"modelType"] integerValue]) {
case LXDModelTypeCoupon:
ModelCls = [KXDCouponTicketModel class];
break;
case LXDModelTypeConvert:
ModelCls = [LXDConvertTicketModel class];
break;
case LXDModelTypeDiscount:
ModelCls = [LXDDiscountTicketModel class];
break;
}
for (NSDictionary * dataDict in jsonDict[@"data"]) {
id item = [(id)[ModelCls alloc] initWithDict: dataDict];
[result addObject: item];
}
[LXDQueue executeInMainQueue: ^{
complete(result);
}];
}];
}
@end
// m实现
#import "KMCNetworkHelper.h"
@implementation LXDTicketViewController
- (void)requestTickets
{
// get request parameters include 'url' and 'parameters'
[LXDNetworkManager POST: PATH(url) parameters: parameters
complete: ^(NSString * JSON, NSError * error) {
// error check
[LXDTicketDataHelper analyseJSON: JSON complete: ^(NSMutableArray * models) {
[self.currentModelSet addObjectsFromArray: models];
}];
}];
}
@end
去model化之后整个项目的业务流程大致可以用下图表示:
这种方式最大的好处在于控制器和视图不再依赖于model的具体类型,这样在服务器返回的json中修改了模型对象字段的时候,修改ModelProtocol的对应实现即可。甚至在以后的版本再添加现金券各种其他票券的时候,只需要在Helper这一环节添加相应的工厂即可完成改动
尾言
去model化是一种有效快捷的松耦合方式,但绝不是万能药。在本文的demo中不难看到笔者使用这一方式最大的原因在于多个cell之间有太多的共性而model的属性字段全不相同。另一方面在这种设计中Helper可能会因为模型对象的增加变得臃肿,需要谨慎使用。
一个好的项目框架总是随着需求改变在不断的调整的,没有绝对最佳的设计方案。但是尝试使用不同的思路去搭建项目可以提升我们的认知,培养对于开发框架设计的认识。
去model化开发的更多相关文章
- 一步步教你开发、部署第一个去中心化应用(Dapp) - 宠物商店
今天我们来编写一个完整的去中心化(区块链)应用(Dapps), 本文可以和编写智能合约结合起来看. 写在前面 阅读本文前,你应该对以太坊.智能合约有所了解,如果你还不了解,建议你先看以太坊是什么除此之 ...
- IM 去中心化概念模型与架构设计
今天打算写写关于 IM 去中心化涉及的架构模型变化和设计思路,去中心化的概念就是说用户的访问不是集中在一个数据中心,这里的去中心是针对数据中心而言的. 站在这个角度而言,实际上并非所有的业务都能做去中 ...
- 小众Tox——大众的“去中心化”聊天软件
★Tox是什么 一个反窥探的开源项目:一种基于DHT(BitTorrent)技术的即时通讯协议:一个为安全而生的加密通讯系统 .美国棱镜计划曝光后,一个名为 irungentoo 的牛人于17天后的2 ...
- kettle系列-4.kettle定制化开发工具类
要说的话这个工具类还是比较简单的,每个方法体都比较小,但用起来还是可以的,把开发中一些常用的步骤封装了下,不用去kettle源码中找相关操作的具体实现了. 算了废话不多了,直接上重点,代码如下: im ...
- vue.js组件化开发实践
前言 公司目前制作一个H5活动,特别是有一定统一结构的活动,都要码一个重复的轮子.后来接到一个基于模板的活动设计系统的需求,便有了下面的内容.借油开车. 组件化 需求一到,接就是怎么实现,技术选型自然 ...
- FineUI大版本升级,外置ExtJS库、去AXD化、表格合计行、表格可编辑单元格的增删改、顶部菜单框架
这是一篇很长的文章,在开始正文之前,请允许我代表目前排名前 20 中唯一的 .Net 开源软件 FineUI 拉下选票: 投票地址: https://code.csdn.net/2013OSSurve ...
- 一个轻client,多语言支持,去中心化,自己主动负载,可扩展的实时数据写服务的实现方案讨论
背景 背景是设计一个实时数据接入的模块,负责接收client的实时数据写入(如日志流,点击流),数据支持直接下沉到HBase上(兴许提供HBase上的查询),或先持久化到Kafka里.方便兴许进行一些 ...
- Android插件化开发---执行未安装apk中的Service
欢迎各位增加我的Android开发群[257053751] 假设你还不知道什么叫插件化开发.那么你应该先读一读之前写的这篇博客:Android插件化开发,初入殿堂 上一篇博客主要从总体角度分析了一下 ...
- NET 平台下的插件化开发内核
.NET 平台下的插件化开发内核(Rabbit Kernel) 每个程序猿都有一个框架梦,曾经在2013年8月15日写过一篇“Koala Framework是什么?我为什么要写这个框架?”的文章, ...
随机推荐
- 【转】AngularJS路由和模板
1. AngularJS路由介绍 AngularJS路由功能是一个纯前端的解决方案,与我们熟悉的后台路由不太一样.后台路由,通过不同的URL会路由到不同的控制器上(controller),再渲染(re ...
- android.content.res.Resources$NotFoundException: String resource ID #0x1
之前忘了记录这个错误,今天又遇到了.唉,人不能纵容自己犯懒,遂记录之. 错误:android.content.res.Resources$NotFoundException: String resou ...
- MessagePack介绍
在项目中,服务端的人需要我研究messagepcak 进行数据的传输,对messagePack的了解就是传输的数据格式都是二进制,可以节省用户的流量,就因为这点 数据格式小,服务端决定采用msgpac ...
- NGINX(四)配置解析
前言 nginx配置解析是在初始化ngx_cycle_t数据结构时,首先解析core模块,然后core模块依次解析自己的子模块. 配置解析过程 nginx调用ngx_conf_parse函数进行配置文 ...
- HDU 2295 Radar dancing links 重复覆盖
就是dancing links 求最小支配集,重复覆盖 精确覆盖时:每次缓存数据的时候,既删除行又删除列(这里的删除列,只是删除表头) 重复覆盖的时候:只删除列,因为可以重复覆盖 然后重复覆盖有一个估 ...
- Codeforces 633D Fibonacci-ish 暴力
题意:1000个元素,每个元素的大小-1e9<=a[i]<=1e9,然后让你重新安排这些元素的位置 获得最长的前缀斐波那契数列 分析:枚举第一个元素和第二个元素,因为在题目元素的范围内,最 ...
- POJ3241 Object Clustering 曼哈顿最小生成树
题意:转换一下就是求曼哈顿最小生成树的第n-k条边 参考:莫涛大神的论文<平面点曼哈顿最小生成树> /* Problem: 3241 User: 96655 Memory: 920K Ti ...
- jquery优化28个建议
我一直在寻找有关jQuery性能优化方面的小窍门,能让我那臃肿的动态网页应用变得轻便些.找了很多文章后,我决定将最好最常用的一些优化性能的建议列出来.我也做了一个jQuery性能优化的简明样式表,你可 ...
- 派遣例程与IRP结构
提到派遣例程,必须理解IRP(I/O Request Package),即"输入/输出请求包"这个重要数据结构的概念.Ring3通过DeviceIoControl等函数向驱动发出I ...
- Win10系统安装
2016正月十一来到了学校,刚刚拿到了姐姐的thinkpad,到学校来想重新安装一下系统并且重新磁盘分区. 上一次也安装过win10,不过基本方法已经忘了,制作的U启动盘也不在了. 首先按照http: ...