一个项目从开发到完成可能会融合进好多设计模式,MVC、MVP、MVVM或者MVC+MVP等各种的组合。
假定老板提出让你做一款美食APP,然后你产品就咔咔咔的去设计了,然后设计了如下的东东:

就这么点东西,你会选择MVP么?显然MVC就已经足够了,如果MVP,那就要多写几个文件。如果写的足够好的话,MVC能够很容易的移至到MVP中。我们直接上一坨代码,标记下几个类文件充当MVC中的角色,发现MVC真的能满足。

老板又说了,你的菜得有个分类啊,凉菜、炒菜、主食、酒水 等等,你得给左侧加个分类啊。我们暂且理解,就百度外卖那个样子。然后产品就是设计去了,设计如下的东东:

一个页面左右两个列表,如果MVC去写倒是也行,但是你要考虑到如下情况
- 如果用MVC写,控制层是不是很臃肿?
- 如果哪天老板说左侧列表我不要了,你砍掉一个功能容易么?会影响到右侧列表么?
- 如果说这个页面再给你个人去做,你怎么去做?代码你怎么合并?
MVP就显示出它的优势了,比如,左侧用MVC设计、右侧也用MVC设计,最后由统一的P层去调用。MVP中嵌套的MVC 我就理解为Model、View、Controller(M和V的协调者)。
说多了没用,先看一个结构图,再上一坨代码,代码里标记下各部分在MVP中充当的角色

最后效果如下:

我们看下Presenter层都做了什么?
- 把各个页面拼接到一起
- 实现各个模块中的协议进而实现:协调分类控制器和菜品控制器联动、处理点击菜品时的业务
Presenter 中的代码如下:
@interface Presenter ()<CategoryTableViewControllerDelegate, DishTableViewControllerDelegate>
/** 分类控制器 */
@property (nonatomic, strong) CategoryTableViewController* categoryVC;
/** 菜品控制器 */
@property (nonatomic, strong) DishTableViewController* dishVC;
@end
@implementation Presenter
#pragma mark -
#pragma mark - life circle
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
// Do any additional setup after loading the view.
self.categoryVC = [[CategoryTableViewController alloc] initWithStyle:(UITableViewStylePlain)];
self.categoryVC.delegate = self;
[self.view addSubview:self.categoryVC.tableView];
[self addChildViewController:self.categoryVC];
self.dishVC = [[DishTableViewController alloc] initWithStyle:(UITableViewStylePlain)];
self.dishVC.delegate = self;
[self.view addSubview:self.dishVC.tableView];
[self addChildViewController:self.dishVC];
[self.categoryVC.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view.mas_top).offset(0);
make.left.equalTo(self.view.mas_left).offset(0);
make.bottom.equalTo(self.view.mas_bottom).offset(0);
make.width.mas_equalTo(@(100));
}];
[self.dishVC.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.categoryVC.tableView.mas_top).offset(0);
make.right.equalTo(self.view.mas_right).offset(0);
make.bottom.equalTo(self.view.mas_bottom).offset(0);
make.left.equalTo(self.categoryVC.tableView.mas_right).offset(0);
}];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark -
#pragma mark - CategoryTableViewControllerDelegate
-(void)categoryTableViewController:(CategoryTableViewController *)controller didSelectCategoryId:(NSInteger)categoryId{
[self.dishVC scrollRowWithCategoryId:categoryId];
}
#pragma mark -
#pragma mark - DishTableViewControllerDelegate
-(void)dishTableViewController:(DishTableViewController *)controller willShowCategoryId:(NSInteger)categoryId{
[self.categoryVC selectRowWithCategoryId:categoryId];
}
-(void)dishTableViewController:(DishTableViewController *)controller didSelectDish:(DishModel *)dishModel{
NSLog(@"当前选中的菜品名称:%@", dishModel.dishName);
}
那么在分类控制器中,接口如下:
@class CategoryTableViewController;
@protocol CategoryTableViewControllerDelegate <NSObject>
@optional
/**
选中分类的时候
@param controller 分类控制器
@param categoryId 当前选中的分类id
*/
-(void)categoryTableViewController:(CategoryTableViewController*)controller didSelectCategoryId:(NSInteger)categoryId;
@end
@interface CategoryTableViewController : UITableViewController
/** 代理 */
@property (nonatomic, weak) id<CategoryTableViewControllerDelegate> delegate;
/**
根据分类ID选择某个分类
@param categoryId 分类id
*/
-(void)selectRowWithCategoryId:(NSInteger)categoryId;
@end
那么在菜品控制器中,接口如下:
@class DishTableViewController;
@class DishModel;
@protocol DishTableViewControllerDelegate <NSObject>
@optional
/**
滚动菜品section变化的时候
@param controller 菜品控制器
@param categoryId 当前最顶部展现的菜品的分类id
*/
-(void)dishTableViewController:(DishTableViewController*)controller willShowCategoryId:(NSInteger)categoryId;
/**
选中一个菜品的时候
@param controller 菜品控制器
@param dishModel 菜品数据模型
*/
-(void)dishTableViewController:(DishTableViewController*)controller didSelectDish:(DishModel*)dishModel;
@end
@interface DishTableViewController : UITableViewController
/** 代理 */
@property (nonatomic, weak) id<DishTableViewControllerDelegate> delegate;
/**
根据分类ID滚动某个菜品
@param categoryId 分类id
*/
-(void)scrollRowWithCategoryId:(NSInteger)categoryId;
@end
拼接两个tableView的时候添加约束并没有达到预期的效果,后来发现是导航的 translucent属性影响,解决办法如下

假如老板又说了,顶部再给我加个地址选择的区域,那么MVP显然优势就来了。加一个地址信息模块,跟Category和Dish平级,模块内部MVC、MVP都无所谓了。只需要在Presenter内部调用对应模块的接口即可。
感谢您阅读完毕,如有疑问,欢迎添加QQ:714387953(蜗牛上高速)。
github:https://github.com/yhl714387953/MVP
如果有错误,欢迎指正,一起切磋,共同进步
如果喜欢可以Follow、Star、Fork,都是给我最大的鼓励。
网友评论