前言

去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在以后发生改变了,那么原有的数据对象是否还能适用

控制器需要在数据源方法中调配不同的cellmodel,耦合过大
这个也是常见的问题之一,通常可以考虑适用工厂模式将调配的业务分离出去,但在本文中采用去model的方式实现

这些问题都有可能导致项目后期维护的过程中变得难以修改,小小的需求改动都会导致代码的大改。笔者的解决方式是制定cellmodel之间对应的两个协议,从而控制器无需理会两者的具体类型。

实现

我在上一篇文章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化开发的更多相关文章

  1. 一步步教你开发、部署第一个去中心化应用(Dapp) - 宠物商店

    今天我们来编写一个完整的去中心化(区块链)应用(Dapps), 本文可以和编写智能合约结合起来看. 写在前面 阅读本文前,你应该对以太坊.智能合约有所了解,如果你还不了解,建议你先看以太坊是什么除此之 ...

  2. IM 去中心化概念模型与架构设计

    今天打算写写关于 IM 去中心化涉及的架构模型变化和设计思路,去中心化的概念就是说用户的访问不是集中在一个数据中心,这里的去中心是针对数据中心而言的. 站在这个角度而言,实际上并非所有的业务都能做去中 ...

  3. 小众Tox——大众的“去中心化”聊天软件

    ★Tox是什么 一个反窥探的开源项目:一种基于DHT(BitTorrent)技术的即时通讯协议:一个为安全而生的加密通讯系统 .美国棱镜计划曝光后,一个名为 irungentoo 的牛人于17天后的2 ...

  4. kettle系列-4.kettle定制化开发工具类

    要说的话这个工具类还是比较简单的,每个方法体都比较小,但用起来还是可以的,把开发中一些常用的步骤封装了下,不用去kettle源码中找相关操作的具体实现了. 算了废话不多了,直接上重点,代码如下: im ...

  5. vue.js组件化开发实践

    前言 公司目前制作一个H5活动,特别是有一定统一结构的活动,都要码一个重复的轮子.后来接到一个基于模板的活动设计系统的需求,便有了下面的内容.借油开车. 组件化 需求一到,接就是怎么实现,技术选型自然 ...

  6. FineUI大版本升级,外置ExtJS库、去AXD化、表格合计行、表格可编辑单元格的增删改、顶部菜单框架

    这是一篇很长的文章,在开始正文之前,请允许我代表目前排名前 20 中唯一的 .Net 开源软件 FineUI 拉下选票: 投票地址: https://code.csdn.net/2013OSSurve ...

  7. 一个轻client,多语言支持,去中心化,自己主动负载,可扩展的实时数据写服务的实现方案讨论

    背景 背景是设计一个实时数据接入的模块,负责接收client的实时数据写入(如日志流,点击流),数据支持直接下沉到HBase上(兴许提供HBase上的查询),或先持久化到Kafka里.方便兴许进行一些 ...

  8. Android插件化开发---执行未安装apk中的Service

    欢迎各位增加我的Android开发群[257053751​] 假设你还不知道什么叫插件化开发.那么你应该先读一读之前写的这篇博客:Android插件化开发,初入殿堂 上一篇博客主要从总体角度分析了一下 ...

  9. NET 平台下的插件化开发内核

    .NET 平台下的插件化开发内核(Rabbit Kernel)   每个程序猿都有一个框架梦,曾经在2013年8月15日写过一篇“Koala Framework是什么?我为什么要写这个框架?”的文章, ...

随机推荐

  1. 静态Web开发 CSS

    二章 CSS 1节CSS介绍 CSS(Cascading Style Sheets) //cascade级联,串联CSS由两个部分组成:选择器,以及一条或多条声明selector{declaratio ...

  2. 可用于Hadoop下的ETL工具——Kettle

    看大家分享了好多hadoop相关的一些内容,我为大家介绍一款ETL工具——Kettle.    Kettle是pentaho公司开源的一款ETL工具,跟hadoop一样,也是java实现,其目的就是做 ...

  3. HDU 1011-Starship Troopers(树形背包)

    题意: 有n个洞,连接像一棵树,每个包含一定数量的怪和价值,给你m个士兵,每个士兵能打20个怪,杀完一个洞的怪可得该洞的价值才可继续打相连的下面的洞(每个士兵只能打一个洞),求获得的最大价值. 分析: ...

  4. javaScript动态给下拉列表框添加选项

    方式一: <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></titl ...

  5. SQL Server 2005如何远程连接数据库?

    SQL Server 2005如何远程连接数据库? 方法/步骤   1 在配置工具中的服务和远程连接的外围应用配置器 --->远程连接-->本地连接和远程连接-->同时使用TCP/I ...

  6. 线性存储结构-LinkedList

    LinkedList内部采用链表的形式构建,是一个双向链表.除了继承List外,还继承了Deque接口,可以当做堆栈结构使用. private static final class Link<E ...

  7. curl命令学习(转载的)

    原文地址: http://www.thegeekstuff.com/2012/04/curl-examples/ curl是网络上常用一个命令,简单来说就是可以上传下载,甚至可以当成下载工具使用,比如 ...

  8. 【暑假】[数学]UVa 1262 Password

    UVa 1262  Password 题目: Password   Time Limit: 3000MS   Memory Limit: Unknown   64bit IO Format: %lld ...

  9. MapReduce 支持的部分数据挖掘算法

    MapReduce 支持的部分数据挖掘算法 MapReduce 能够解决的问题有一个共同特点:任务可以被分解为多个子问题,且这些子问题相对独立,彼此之间不会有牵制,待并行处理完这些子问题后,任务便被解 ...

  10. HDU1890 Robotic Sort Splay tree反转,删除

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1890 题目中涉及数的反转和删除操作,需要用Splay tree来实现.首先对数列排序,得到每个数在数列 ...