做一个用户登录中心,那么首先。。。
- 使用何种设计模式 ,MVC ,MVP ,MVVM?
为什么第一个问题要关注选择何种设计模式 ,因为如果你不关心设计模式,慢慢的你会发现UserCenterViewController 里面的代码会满满积累,最终变成一个Massive ViewController ,想快速的修bug 或者增加功能变得异常麻烦。

今天谈论的设计模式
- MVC
- MVP
- MVVM
三者的共同点表展在Model 和 View
Models : 负责数据的操作。
View : UI 展示,没什么好说的。
三者的差异主要体现在粘合View和Model的方式:
1.Controller接收View的操作事件,根据事件不同,或者调用Model的接口进行数据操作,或者进行View的跳转,从而也意味着一个Controller可以对应多个View。Controller对View的实现不太关心,只会被动地接收,Model的数据变更不通过Controller直接通知View,通常View采用观察者模式监听Model的变化。
2.Presenter,与Controller一样,接收View的命令,对Model进行操作;与Controller不同的是Presenter会反作用于View,Model的变更通知首先被Presenter获得,然后Presenter再去更新View。一个Presenter只对应于一个View。根据Presenter和View对逻辑代码分担的程度不同,这种模式又有两种情况:Passive View和Supervisor Controller。
3.ViewModel,注意这里的“Model”指的是View的Model,跟上面那个Model不是一回事。所谓View的Model就是包含View的一些数据属性和操作的这么一个东东,这种模式的关键技术就是数据绑定(data binding),View的变化会直接影响ViewModel,ViewModel的变化或者内容也会直接体现在View上。这种模式实际上是框架替应用开发者做了一些工作,开发者只需要较少的代码就能实现比较复杂的交互。
Coding time
MVC

Model:
@interface AccountInfo : NSObject
@property (nonatomic,copy) NSString *userName;
@property (nonatomic,copy) NSString *mobile;
@property (nonatomic,copy) NSString *avatarUrl;
@end
Controller & View:
@interface UserCenterViewController ()
@property (nonatomic,strong) UILabel *userDescLabel;
@property (nonatomic,strong) UIButtom *renameButton;
@property (nonatomic,strong) AccountInfo *accountInfo;
@end
@implementation UserCenterViewController
- (instancetype)initWithAccountInfo:(AccountInfo *)accountInfo
{
self = [super init];
if (self)
{
self.accountInfo = accountInfo;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.userDescLabel];
[self.renameButton addTarget:self action:@selector(renameAction) forControlEvents:UIControlEventTouchUpInside] ;
}
- (void)renameAction
{
self.accountInfo.userName = @"vedon";
self.userDescLabel.text = [NSString stringWithFormat:@"%@ %@",self.accountInfo.userName,self.accountInfo.mobile];
}
@end
调用方法:
AccountInfo *accountInfo = [AccountInfo new];
UserCenterViewController *userCenterController = [[UserCenterViewController alloc] initWithAccountInfo:accountInfo];
[self presentViewController:userCenterController animated:YES completion:nil];
一开始所有的controller 都是如此纯洁,随着需求迭代,你懂的。。。很早之前就有一篇文章提出了这个问题。MVC 很容易写出很臃肿的Controller ,但是它有一个很明显的优点,开发效率高!
如何避臃肿?
- 尽量避免在Controller 的view上添加过多的view.可以把有复杂层次结构的view抽离成一层contentView,提高复用率。
- 在controller 中写业务逻辑代码,要注意抽离成单独的小模块,形成复用模块。
- 动画代码抽离到单独的模块。
提高代码的复用率,带来的好处可能是意想不到的。在开发夸克浏览器的时候,云同步底层是直接拿UC浏览器的,不需要任何改动就可以使用了。
MVP

在MVP 里面,UIViewController 实际上当View来使用了,Presenters充当了Controller的角色。这种方式提高了可测试性,但是付出的代价是开发效率降低了。
Model:
@interface AccountInfo : NSObject
@property (nonatomic,copy) NSString *userName;
@property (nonatomic,copy) NSString *mobile;
@end
Presenter:
@interface UserCenterPresenter : NSObject<UserCenterViewProtocol>
@property (nonatomic,weak) id<UserCenterViewProtocol> view;
@property (nonatomic,strong) AccountInfo *accountInfo;
@end
@implementation Presenter
- (void)showUserInfo
{
NSString *desc = [NSString stringWithFormat:@"%@ %@",self.accountInfo.userName,self.accountInfo.mobile]
[self.view showUserInfoWithDesc:desc];
}
- (void)renameAction
{
self.accountInfo.userName = @"vedon";
[self showUserInfo];
}
@end
Protocol:
@protocol UserCenterViewProtocol <NSObject>
- (void)showUserInfoWithDesc:(NSString *)desc;
@end
View/Controller:
@interface UserCenterViewController ()
@property (nonatomic,strong) UILabel *userDescLabel;
@property (nonatomic,strong) UIButtom *renameButton;
@property (nonatomic,strong) UserCenterPresenter *presenter;
@end
@implementation UserCenterViewController
- (instancetype)initWithAccountInfo:(AccountInfo *)accountInfo presenter:(UserCenterPresenter *)presenter
{
self = [super init];
if (self)
{
self.presenter = presenter;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.userDescLabel];
[self.renameButton addTarget:self.presenter action:@selector(renameAction) forControlEvents:UIControlEventTouchUpInside] ;
}
#pragma mark - UserCenterViewProtocol
- (void)showUserInfoWithDesc:(NSString *)desc
{
self.userDescLabel.text = desc;
}
@end
调用方法:
AccountInfo *accountInfo = [AccountInfo new];
UserCenterPresenter *presenter = [UserCenterPresenter new];
presenter.accountInfo = accountInfo;
UserCenterViewController *userCenterController = [[UserCenterViewController alloc] initWithAccountInfo:accountInfo presenter:presenter];
presenter.view = userCenterController;
[self presentViewController:userCenterController animated:YES completion:nil];
MVP从更早的MVC框架演变过来的一种框架,与MVC有一定的相似性。MVP框架由3部分组成:View负责显示,Presenter负责逻辑处理(一个Presenter只对应于一个View或者是一类view),Model提供数据。MVP与MVC之间最主要的区别在控制层上,在MVP框架中,View与Model并不直接交互,所有的交互放在Presenter中;而在MVC里,View与Model会直接产生一定的交互。MVP的Presenter是框架的控制者,承担了大量的逻辑操作,而MVC的Controller更多时候承担一种转发的作用。因此在App中引入MVP的原因,是为了将此前在Controller中包含的大量逻辑操作放到控制层中,避免Controller的臃肿。
现在假如我们通过上面的方式解决了部分C太重的问题,我们看上图Presenter涉及到的调用非常复杂,这几个交互可以说是业务代码的bug聚集地,单向的数据流能规避很多问题。
MVVM

Model :
@interface AccountInfo : NSObject
@property (nonatomic,copy) NSString *userName;
@property (nonatomic,copy) NSString *mobile;
Protocol:
@protocol UserCenterViewModelProtocol <NSObject>
- (void)renameAction;
@end
ViewModel:
typedef void (^UserInfoDidChangeCallback)(NSString *desc);
@interface UserCenterViewModel : NSObject<UserCenterViewProtocol>
@property (nonatomic,weak) id<UserCenterViewProtocol> view;
@property (nonatomic,strong) AccountInfo *accountInfo;
@property (nonatomic,strong) UserInfoDidChangeCallback userInfoDidChangeCallback;
- (void)showUserInfo;
@end
@implementation UserCenterViewModel
- (void) renameAction
{
self.accountInfo.userName = @"vedon";
if(self.userInfoDidChangeCallback)
{
NSString *desc = [NSString stringWithFormat:@"%@ %@",self.accountInfo.userName,self.accountInfo.mobile]
self. userInfoDidChangeCallback(desc)
}
}
@end
Controller:
@interface UserCenterViewController ()
@property (nonatomic,strong) UILabel *userDescLabel;
@property (nonatomic,strong) UIButtom *renameButton;
@property (nonatomic,strong) UserCenterViewModel *viewModel;
@end
@implementation UserCenterViewController
- (instancetype)initWithAccountInfo:(AccountInfo *)accountInfo viewModel:(UserCenterViewModel *)viewModel
{
self = [super init];
if (self)
{
self.viewModel = viewModel;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.userDescLabel];
self.viewModel. userInfoDidChangeCallback = ^(NSString *desc){
self.userDescLabel.text = desc;
};
[self.renameButton addTarget:self.viewModel action:@selector(renameAction) forControlEvents:UIControlEventTouchUpInside] ;
}
@end
MVP 和MVVM 非常相似!事实上,MVVM的View要比MVP中的View承担的责任多。因为前者通过ViewModel的设置绑定来更新状态,而后者只监听Presenter的事件但并不会对自己有什么更新。MVVM 和MVP带来的好处非常明显,它非常清晰的拆分开UI层和数据层 ,使单元测试更加容易。在复杂的表示逻辑下,优点会越发明显!
对于ViewModel 的角色定位一定要清晰,它的存在是为了抽离ViewController 中的展示业务逻辑,视图操作逻辑依然是在Controller 实现。那么在它里面就不应该出现Push/Present 以及其他UIKit 的东西。而展示业务逻辑是什么?就是负责将数据业务层提供的数据转化为界面展示所需要,Model的信息可能并不能正确体现View状态,所以需要经过ViewModel的转化。
MMVM 的关键在与数据绑定,查了一下,已有开源框架在objc 的基础上实现了一套数据绑定的框架。
题外话: 业务拆分
无论采取何种模式开发,业务逻辑就在那里,不多不少。ViewController 使用category 的形式进行拆分。这样一来,Massive View Controller
此外,UI 组件设置样式的代码也可以通过 Category 的方法拆出去了。例如 :UILabel + PriceStyle 这个 Category 里,UIButton 也是一样的处理方式。在使用的时候,直接通过 Category 里的方法获取实例即可.
张三丰教无忌太极剑法,教完了却让他忘掉,这是为什么呢
对话好像是这样:“还记得吗?”
“全都记得。”
“现在呢?”
“已经忘却了一小半。”
“啊,已经忘了一大半。”
“不坏不坏,忘得真快,那么现在呢?”
“已经全都忘了,忘得干干净净。”
“好了,你上吧。”
网友评论