去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是什么?我为什么要写这个框架?”的文章, ...
随机推荐
- 选择select框跳出信息
<html > <body > <select type="select" name=s1 onChange=alert("你选择了&quo ...
- Ext入门学习系列(二)弹出窗体
第二章 弹出窗体 上节学习了Ext的环境搭建和最基本的一个操作——弹出对话框,作为一个引子,本节讲述如何弹出一个新窗体,从实例讲解Ext的基本运行原理. 一.Ext的窗体长什么样? 先来看看几个效果, ...
- HDU 5638 Toposort 线段树+贪心
题意:bc round 74 分析: 参考下普通的用堆维护求字典序最小拓扑序, 用某种数据结构维护入度小于等于k的所有点, 每次找出编号最小的, 并相应的减少k即可. 这个数据结构可以用线段树, 建立 ...
- FZU 2129 子序列个数
Problem Description 子序列的定义:对于一个序列a=a[1],a[2],......a[n].则非空序列a'=a[p1],a[p2]......a[pm]为a的一个子序列,其中1& ...
- 容斥原理——uva 10325 The Lottery
首先推荐一篇介绍容斥原理很好的博客http://www.cppblog.com/vici/archive/2011/09/05/155103.html 题意:求1~n中不能被给定m个数中任意一个数整除 ...
- Error starting static Resources java.lang.IllegalArgumentException: Document base D:\Program Files\apache-tomcat-xxx\webapps\xxx does not exist or is not a readable directory
网上的答案大多数如下: 但并没有解决我的问题 经过我的观察: 在tomcat的server.xml有Lottery项目描述,但实际上,该项目已被我删除,不存在于webapps中了 该行Cont ...
- 【现代程序设计】【Homework-01】
1维的最大子数组之和 对于1维的最大子数组之和 假设f[i]表示:对于1..i这个序列中,包含i这个元素的最大序列的值 则对于f[i],0<i<=n; 应该有 f[i]=max(a[i], ...
- PC问题-使用BAT方法清理Delphi临时文件
@echo offdel /S *.~*del /S *.dcudel /S *.dskdel /S *.hppdel /S *.ddpdel /S *.mpsdel /S *.mptdel /S * ...
- ACCESS-如何多数据库查询(跨库查询)
测试通过:ACCESSselect * from F:\MYk.mdb.tablename说明:1.查询语句2.来原于哪(没有密码是个路径)3.查询的表名 ====================== ...
- hdoj 2829 Lawrence 四边形不等式优化dp
dp[i][j]表示前i个,炸j条路,并且最后一个炸在i的后面时,一到i这一段的最小价值. dp[i][j]=min(dp[i][k]+w[k+1][i]) w[i][j]表示i到j这一段的价值. # ...