文章轉(zhuǎn)載自: Sindri的小巢
原文鏈接:http://www.reibang.com/p/2b2290fa0beb
前言
去model化
是一種框架設(shè)計(jì)上的做法嗤练,其中的model
并不是指架構(gòu)中的model
層哮翘,套用Casa大神博客中的原文就是:
model化就是使用數(shù)據(jù)對(duì)象侄榴,
去model化
就是不使用數(shù)據(jù)對(duì)象骤菠。
常見的去model化
做法是使用字典保存數(shù)據(jù)信息骡尽,然后提供一個(gè)reformer
負(fù)責(zé)將這些字典數(shù)據(jù)轉(zhuǎn)換成View層可展示的信息威恼,其流程圖如下:
更詳細(xì)的理論知識(shí)可以看Casa大神的去model化和數(shù)據(jù)對(duì)象积仗。本文基于Casa大神的實(shí)踐基礎(chǔ)使用另外一種去model化
的實(shí)現(xiàn)方式疆拘。
使用背景
在很早之前就看過大神的文章,不過一直沒有去嘗試這種做法斥扛。在筆者最近跳入新坑之后入问,總算是有了這么一次機(jī)會(huì)。需求是存在著三個(gè)非常相似的cell
稀颁,但分別對(duì)應(yīng)著不同的數(shù)據(jù)model
:
總結(jié)三個(gè)cell
都需要的展示數(shù)據(jù)包括:
- 產(chǎn)品名稱
- 使用條件
- 截止日期
- 背景圖片*
此外芬失,優(yōu)惠信息
屬于第一個(gè)和第二個(gè)獨(dú)有的。現(xiàn)在這一需求存在的問題主要有這么三點(diǎn):
三種數(shù)據(jù)對(duì)象在服務(wù)器返回的屬性字段中命名差別大
這是大部分的應(yīng)用都存在的一個(gè)問題匾灶,但是本文中的數(shù)據(jù)對(duì)象有一個(gè)顯著的特點(diǎn)是它們對(duì)應(yīng)顯示的cell
存在很大的相似度棱烂,可以被轉(zhuǎn)換成相似的展示數(shù)據(jù)
三種
cell
可以封裝成一種,卻分別對(duì)應(yīng)著不同的數(shù)據(jù)對(duì)象
這里涉及cell
和數(shù)據(jù)對(duì)象的對(duì)接問題阶女,如果cell
在以后發(fā)生改變了颊糜,那么原有的數(shù)據(jù)對(duì)象是否還能適用
控制器需要在數(shù)據(jù)源方法中調(diào)配不同的
cell
和model
哩治,耦合過大
這個(gè)也是常見的問題之一,通吵挠悖可以考慮適用工廠模式將調(diào)配的業(yè)務(wù)分離出去业筏,但在本文中采用去model
的方式實(shí)現(xiàn)
這些問題都有可能導(dǎo)致項(xiàng)目后期維護(hù)的過程中變得難以修改,小小的需求改動(dòng)都會(huì)導(dǎo)致代碼的大改鸟赫。筆者的解決方式是制定cell
和model
之間對(duì)應(yīng)的兩個(gè)協(xié)議蒜胖,從而控制器無需理會(huì)兩者的具體類型。
實(shí)現(xiàn)
我在上一篇文章MVC架構(gòu)雜談中提到過M
層的業(yè)務(wù)邏輯放在model
中抛蚤,雖然本文要去model化
台谢,但只是去除屬性對(duì)象,自身的邏輯處理還保留著岁经。下面是筆者去model化的協(xié)議圖以及協(xié)議聲明屬性:
@protocol LXDTicketModelProtocol <NSObject>
@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<NSObject>
- (void)configurateCellWithModel:
(id<LXDTicketModelProtocol>)model;
@end
對(duì)于本文之中這種存在共同顯示效果的model
朋沮,可以聲明一個(gè)包含多個(gè)readonly
屬性的協(xié)議,讓這些模型對(duì)象在協(xié)議的getter
方法中執(zhí)行數(shù)據(jù)->展示這一過程的業(yè)務(wù)邏輯缀壤,而model
自身只需簡(jiǎn)單的持有字典數(shù)據(jù)即可:
以LXDCouponTicketModel
為例樊拓,協(xié)議的實(shí)現(xiàn)代碼如下:
// h文件
@interface LXDCouponTicketModel: NSObject<LXDTicketModelProtocol>
@end
// m實(shí)現(xiàn)
@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;
}
通過讓三個(gè)數(shù)據(jù)對(duì)象實(shí)現(xiàn)這個(gè)協(xié)議,筆者將要展示的數(shù)據(jù)結(jié)果進(jìn)行統(tǒng)一塘慕。在這種情況下骑脱,封裝成單個(gè)的cell也無需關(guān)心model的具體類型是什么,只需實(shí)現(xiàn)針對(duì)單元格配置的協(xié)議方法獲取展示的數(shù)據(jù)即可:
// h文件
@interface LXDTicketCell: UITableViewCell<LXDTicketCellProtocol>
@end
// m實(shí)現(xiàn)
#define LXDCommonColor [UIColor colorWithRed: 253/255. green: 99/255. blue: 99/255. alpha: 1]
@implementation LXDTicketCell
- (void)configurateWithModel: (id<LXDTicketModelProtocol>)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
三個(gè)問題前兩個(gè)已經(jīng)解決了:通過協(xié)議統(tǒng)一數(shù)據(jù)對(duì)象的展示效果苍糠,這時(shí)候并不需要model保存多個(gè)屬性對(duì)象,只需要在適當(dāng)?shù)臅r(shí)候直接從字典中獲取數(shù)據(jù)并執(zhí)行數(shù)據(jù)可視化這一邏輯即可啤誊。cell也不會(huì)受限于傳入的參數(shù)類型岳瞭,只需要簡(jiǎn)單的調(diào)用協(xié)議方法獲取需要的數(shù)據(jù)即可。那么最后一個(gè)控制器的協(xié)調(diào)問題就變得簡(jiǎn)單了:
// m實(shí)現(xiàn)
@interface LXDTicketViewController ()
@property (nonatomic, strong) NSMutableArray< id<LXDTicketModelProtocol> > * couponTickets;
@property (nonatomic, strong) NSMutableArray< id<LXDTicketModelProtocol> > * discountTickets;
@property (nonatomic, strong) NSMutableArray< id<LXDTicketModelProtocol> > * 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<LXDTicketCellProtocol>)cell configurateCellWithModel: [self modelWithIndexPath: indexPath]]; }
return cell;
}
#pragma mark - Data Generator
- (id<LXDTicketModelProtocol>)modelWithIndexPath: (NSIndexPath *)indexPath
{
return self.currentModelSet[indexPath.row];
}
- (NSMutableArray< id<LXDTicketModelProtocol> > *)currentModelSet
{
switch (_ticketType) {
case KMCTicketTypeCoupon:
return _couponTickets;
case KMCTicketTypeDiscount:
return _discountTickets;
case KMCTicketTypeConvert:
return _convertTickets;
}
}
@end```
當(dāng)`cell`和`model`共同通過協(xié)議的方式實(shí)現(xiàn)交流的時(shí)候蚊锹,控制器存儲(chǔ)的數(shù)據(jù)源也就可以不關(guān)心這些對(duì)象的具體類型了瞳筏。通過泛型聲明多個(gè)數(shù)據(jù)源,控制器此時(shí)的職責(zé)僅僅是根據(jù)狀態(tài)機(jī)的改變決定使用哪個(gè)數(shù)據(jù)源來展示而已牡昆。當(dāng)然姚炕,雖然筆者統(tǒng)一了這三個(gè)數(shù)據(jù)源的類型,但是歸根到底總要根據(jù)服務(wù)器返回的json創(chuàng)建不同的數(shù)據(jù)對(duì)象存放到這些數(shù)據(jù)源中丢烘。如果把這個(gè)業(yè)務(wù)放在控制器中原本就達(dá)不到松耦合的作用柱宦,因此引入一個(gè)中間人`Helper`來完成這個(gè)業(yè)務(wù):
// h文件
@interface LXDTicketDataHelper: NSObject
- (void)anaylseJSON: (NSString *)JSON complete: (void(^)(NSMutableArray< id<LXDTicketModelProtocol> > *)models);
@end
// m實(shí)現(xiàn)
import "LXDCouponTicketModel.h"
import "LXDConvertTicketModel.h"
import "LXDDiscountTicketModel.h"
@implementation LXDTicketDataHelper
-
(void)anaylseJSON: (NSString *)JSON complete: (void(^)(NSMutableArray< id<LXDTicketModelProtocol> > *)models)
{
NSParameterAssert(JSON);
NSParameterAssert(complete);
[LXDQueue executeInGlobalQueue: ^{
Class ModelCls = NULL;
NSDictionary * jsonDict = [NSDictionary dictionaryWithJSON: JSON];
NSMutableArray< id<LXDTicketModelProtocol> > * results = @[].mutableCopy;
// 使用switch簡(jiǎn)單工廠,如果case太多時(shí)播瞳,使用繼承關(guān)系的工廠會(huì)更好
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<LXDTicketModelProtocol>)[ModelCls alloc] initWithDict: dataDict]; [result addObject: item]; } [LXDQueue executeInMainQueue: ^{ complete(result); }]; }];
}
@end
// m實(shí)現(xiàn)
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化`之后整個(gè)項(xiàng)目的業(yè)務(wù)流程大致可以用下圖表示:
![](http://upload-images.jianshu.io/upload_images/1135052-d3a240701a54c749.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
這種方式最大的好處在于控制器和視圖不再依賴于`model`的具體類型掸刊,這樣在服務(wù)器返回的json中修改了模型對(duì)象字段的時(shí)候,修改ModelProtocol的對(duì)應(yīng)實(shí)現(xiàn)即可赢乓。甚至在以后的版本再添加現(xiàn)金券各種其他票券的時(shí)候忧侧,只需要在Helper這一環(huán)節(jié)添加相應(yīng)的工廠即可完成改動(dòng)
####尾言
`去model化`是一種有效快捷的松耦合方式石窑,但絕不是萬能藥。在本文的demo中不難看到筆者使用這一方式最大的原因在于多個(gè)`cell`之間有太多的共性而`model`的屬性字段全不相同蚓炬。另一方面在這種設(shè)計(jì)中Helper可能會(huì)因?yàn)槟P蛯?duì)象的增加變得臃腫松逊,需要謹(jǐn)慎使用。一個(gè)好的項(xiàng)目框架總是隨著需求改變?cè)诓粩嗟恼{(diào)整的肯夏,沒有絕對(duì)最佳的設(shè)計(jì)方案经宏。但是嘗試使用不同的思路去搭建項(xiàng)目可以提升我們的認(rèn)知,培養(yǎng)對(duì)于開發(fā)框架設(shè)計(jì)的認(rèn)識(shí)熄捍。