中国领先的IT技术网站
|
|

杂谈: MVC/MVP/MVVM (二)

本文为回答一位朋友关于MVC/MVP/MVVM架构方面的疑问所写, 旨在介绍iOS下MVC/MVP/MVVM三种架构的设计思路以及各自的优缺点。

作者:佚名来源:iOS大全|2017-04-01 08:30

【沙龙】51CTO诚邀您9月23号和多位技术大咖一起聊智能CDN的优化之路,抓紧时间哦!


  • MVP

MVC的缺点在于并没有区分业务逻辑和业务展示, 这对单元测试很不友好. MVP针对以上缺点做了优化, 它将业务逻辑和业务展示也做了一层隔离, 对应的就变成了MVCP. M和V功能不变, 原来的C现在只负责布局, 而所有的逻辑全都转移到了P层.

对应关系如图所示:  

业务场景没有变化, 依然是展示三种数据, 只是三个MVC替换成了三个MVP(图中我只画了Blog模块), UserVC负责配置三个MVP(新建各自的VP, 通过VP建立C, C会负责建立VP之间的绑定关系), 并在合适的时机通知各自的P层(之前是通知C层)进行数据获取, 各个P层在获取到数据后进行相应处理, 处理完成后会通知绑定的View数据有所更新, V收到更新通知后从P获取格式化好的数据进行页面渲染, UserVC最后将已经渲染好的各个View进行布局即可. 另外, V层C层不再处理任何业务逻辑, 所有事件触发全部调用P层的相应命令, 具体到代码中如下:

  1. @interface BlogPresenter : NSObject 
  2.  
  3.   
  4.  
  5. + (instancetype)instanceWithUserId:(NSUInteger)userId; 
  6.  
  7.   
  8.  
  9. - (NSArray *)allDatas;//业务逻辑移到了P层 和业务相关的M也跟着到了P层 
  10.  
  11. - (void)refreshUserBlogsWithCompletionHandler:(NetworkTaskCompletionHander)completionHandler; 
  12.  
  13. - (void)loadMoreUserBlogsWithCompletionHandler:(NetworkTaskCompletionHander)completionHandler; 
  14.  
  15.   
  16.  
  17. @end 
  18.  
  19.  
  20. @interface BlogPresenter() 
  21.  
  22.   
  23.  
  24. @property (assign, nonatomic) NSUInteger userId; 
  25.  
  26. @property (strong, nonatomic) NSMutableArray *blogs; 
  27.  
  28. @property (strong, nonatomic) UserAPIManager *apiManager; 
  29.  
  30.   
  31.  
  32. @end 
  33.  
  34.   
  35.  
  36. @implementation BlogPresenter 
  37.  
  38.   
  39.  
  40. + (instancetype)instanceWithUserId:(NSUInteger)userId { 
  41.  
  42.     return [[BlogPresenter alloc] initWithUserId:userId]; 
  43.  
  44.  
  45.   
  46.  
  47. - (instancetype)initWithUserId:(NSUInteger)userId { 
  48.  
  49.     if (self = [super init]) { 
  50.  
  51.         self.userId = userId; 
  52.  
  53.         self.apiManager = [UserAPIManager new]; 
  54.  
  55.         //...略 
  56.  
  57.     } 
  58.  
  59.  
  60.   
  61.  
  62. #pragma mark - Interface 
  63.  
  64.   
  65.  
  66. - (NSArray *)allDatas { 
  67.  
  68.     return self.blogs; 
  69.  
  70.  
  71. //提供给外层调用的命令 
  72.  
  73. - (void)refreshUserBlogsWithCompletionHandler:(NetworkTaskCompletionHander)completionHandler { 
  74.  
  75.   
  76.  
  77.     [self.apiManager refreshUserBlogsWithUserId:self.userId completionHandler:^(NSError *error, id result) { 
  78.  
  79.         if (!error) { 
  80.  
  81.   
  82.  
  83.             [self.blogs removeAllObjects];//清空之前的数据 
  84.  
  85.             for (Blog *blog in result) { 
  86.  
  87.                 [self.blogs addObject:[BlogCellPresenter presenterWithBlog:blog]]; 
  88.  
  89.             } 
  90.  
  91.         } 
  92.  
  93.         completionHandler ? completionHandler(error, result) : nil; 
  94.  
  95.     }]; 
  96.  
  97.  
  98. //提供给外层调用的命令 
  99.  
  100. - (void)loadMoreUserBlogsWithCompletionHandler:(NetworkTaskCompletionHander)completionHandler { 
  101.  
  102.     [self.apiManager loadMoreUserBlogsWithUserId:self.userId completionHandler...] 
  103.  
  104.  
  105.   
  106.  
  107. @end 
  108.  
  109.  
  110. @interface BlogCellPresenter : NSObject 
  111.  
  112.   
  113.  
  114. + (instancetype)presenterWithBlog:(Blog *)blog; 
  115.  
  116.   
  117.  
  118. - (NSString *)authorText; 
  119.  
  120. - (NSString *)likeCountText; 
  121.  
  122.   
  123.  
  124. - (void)likeBlogWithCompletionHandler:(NetworkTaskCompletionHander)completionHandler; 
  125.  
  126. - (void)shareBlogWithCompletionHandler:(NetworkTaskCompletionHander)completionHandler; 
  127.  
  128. @end 
  129.  
  130.  
  131. @implementation BlogCellPresenter 
  132.  
  133.   
  134.  
  135. - (NSString *)likeCountText { 
  136.  
  137.     return [NSString stringWithFormat:@"赞 %ld", self.blog.likeCount]; 
  138.  
  139.  
  140.   
  141.  
  142. - (NSString *)authorText { 
  143.  
  144.     return [NSString stringWithFormat:@"作者姓名: %@", self.blog.authorName]; 
  145.  
  146.  
  147. //    ...略 
  148.  
  149. - (void)likeBlogWithCompletionHandler:(NetworkTaskCompletionHander)completionHandler { 
  150.  
  151.   
  152.  
  153.     [[UserAPIManager new] likeBlogWithBlogId:self.blogId userId:self.userId completionHandler:^(NSError *error, id result) { 
  154.  
  155.         if (error) { 
  156.  
  157.             //do fail 
  158.  
  159.         } else { 
  160.  
  161.             //do success 
  162.  
  163.             self.blog.likeCount += 1; 
  164.  
  165.         } 
  166.  
  167.         completionHandler ? completionHandler(error, result) : nil; 
  168.  
  169.     }]; 
  170.  
  171.  
  172. //    ...略 
  173.  
  174. @end  

BlogPresenter和BlogCellPresenter分别作为BlogViewController和BlogCell的P层, 其实就是一系列业务逻辑的集合. BlogPresenter负责获取Blogs原始数据并通过这些原始数据构造BlogCellPresenter, 而BlogCellPresenter提供格式化好的各种数据以供Cell渲染, 另外, 点赞和分享的业务现在也转移到了这里.

业务逻辑被转移到了P层, 此时的V层只需要做两件事:

1.监听P层的数据更新通知, 刷新页面展示.

2.在点击事件触发时, 调用P层的对应方法, 并对方法执行结果进行展示.

  1. @interface BlogCell : UITableViewCell 
  2.  
  3. @property (strong, nonatomic) BlogCellPresenter *presenter; 
  4.  
  5. @end 
  6.  
  7.  
  8. @implementation BlogCell 
  9.  
  10.   
  11.  
  12. - (void)setPresenter:(BlogCellPresenter *)presenter { 
  13.  
  14.     _presenter = presenter; 
  15.  
  16.     //从Presenter获取格式化好的数据进行展示 
  17.  
  18.     self.authorLabel.text = presenter.authorText; 
  19.  
  20.     self.likeCountLebel.text = presenter.likeCountText; 
  21.  
  22. //    ...略 
  23.  
  24.  
  25.   
  26.  
  27. #pragma mark - Action 
  28.  
  29.   
  30.  
  31. - (void)onClickLikeButton:(UIButton *)sender { 
  32.  
  33.     [self.presenter likeBlogWithCompletionHandler:^(NSError *error, id result) { 
  34.  
  35.         if (!error) {//页面刷新 
  36.  
  37.             self.likeCountLebel.text = self.presenter.likeCountText; 
  38.  
  39.         } 
  40.  
  41. //        ...略 
  42.  
  43.     }]; 
  44.  
  45.  
  46.   
  47.  
  48. @end  

而C层做的事情就是布局和PV之间的绑定(这里可能不太明显, 因为BlogVC里面的布局代码是TableViewDataSource, PV绑定的话, 因为我偷懒用了Block做通知回调, 所以也不太明显, 如果是Protocol回调就很明显了), 代码如下:

  1. @interface BlogViewController : NSObject 
  2.  
  3.   
  4.  
  5. + (instancetype)instanceWithTableView:(UITableView *)tableView presenter:(BlogPresenter)presenter; 
  6.  
  7.   
  8.  
  9. - (void)setDidSelectRowHandler:(void (^)(Blog *))didSelectRowHandler; 
  10.  
  11. - (void)fetchDataWithCompletionHandler:(NetworkCompletionHandler)completionHandler; 
  12.  
  13. @end   

  

  

BlogViewController现在不再负责实际的数据获取逻辑, 数据获取直接调用Presenter的相应接口, 另外, 因为业务逻辑也转移到了Presenter, 所以TableView的布局用的也是Presenter.allDatas. 至于Cell的展示, 我们替换了原来大量的Set方法, 让Cell自己根据绑定的CellPresenter做展示. 毕竟现在逻辑都移到了P层, V层要做相应的交互也必须依赖对应的P层命令, 好在V和M仍然是隔离的, 只是和P耦合了, P层是可以随意替换的, M显然不行, 这是一种折中.

最后是Scene, 它的变动不大, 只是替换配置MVC为配置MVP, 另外数据获取也是走P层, 不走C层了(然而代码里面并不是这样的):

  1. - (void)configuration { 
  2.  
  3.   
  4.  
  5. //    ...其他设置 
  6.  
  7.     BlogPresenter *blogPresenter = [BlogPresenter instanceWithUserId:self.userId]; 
  8.  
  9.     self.blogViewController = [BlogViewController instanceWithTableView:self.blogTableView presenter:blogPresenter]; 
  10.  
  11.     [self.blogViewController setDidSelectRowHandler:^(Blog *blog) { 
  12.  
  13.         [self.navigationController pushViewController:[BlogDetailViewController instanceWithBlog:blog] animated:YES]; 
  14.  
  15.     }]; 
  16.  
  17. //    ...略 
  18.  
  19.  
  20.   
  21.  
  22. - (void)fetchData { 
  23.  
  24.   
  25.  
  26. //        ...略 
  27.  
  28.     [self.userInfoVC fetchData]; 
  29.  
  30.     [HUD show]; 
  31.  
  32.     [self.blogViewController fetchDataWithCompletionHandler:^(NSError *error, id result) { 
  33.  
  34.         [HUD hide]; 
  35.  
  36.     }]; 
  37.  
  38. //还是因为懒, 用了Block走C层转发会少写一些代码, 如果是Protocol或者KVO方式就会用self.blogViewController.presenter了 
  39.  
  40. //不过没有关系, 因为我们替换MVC为MVP是为了解决单元测试的问题, 现在的用法完全不影响单元测试, 只是和概念不符罢了. 
  41.  
  42. //        ...略 
  43.  
  44.  

上面的例子中其实有一个问题, 即我们假定: 所有的事件都是由V层主动发起且一次性的. 这其实是不成立的, 举个简单的例子: 类似微信语音聊天之类的页面, 点击语音Cell开始播放, Cell展示播放动画, 播放完成动画停止, 然后播放下一条语音.

在这个播放场景中, 如果CellPresenter还是像上面一样仅仅提供一个playWithCompletionHandler的接口是行不通的. 因为播放完成后回调肯定是在C层, C层在播放完成后会发现此时执行播放命令的CellPresenter无法通知Cell停止动画, 即事件的触发不是一次性的. 另外, 在播放完成后, C层遍历到下一个待播放CellPresenterX调用播放接口时, CellPresenterX因为并不知道它对应的Cell是谁, 当然也就无法通知Cell开始动画, 即事件的发起者并不一定是V层.

针对这些非一次性或者其他层发起事件, 处理方法其实很简单, 在CellPresenter加个Block属性就行了, 因为是属性, Block可以多次回调, 另外Block还可以捕获Cell, 所以也不担心找不到对应的Cell. 大概这样:

  1. @interface VoiceCellPresenter : NSObject 
  2.  
  3.   
  4.  
  5. @property (copy, nonatomic) void(^didUpdatePlayStateHandler)(NSUInteger); 
  6.  
  7.   
  8.  
  9. - (NSURL *)playURL; 
  10.  
  11. @end 
  12.  
  13.  
  14. @implementation VoiceCell 
  15.  
  16.   
  17.  
  18. - (void)setPresenter:(VoiceCellPresenter *)presenter { 
  19.  
  20.     _presenter = presenter; 
  21.  
  22.   
  23.  
  24.     if (!presenter.didUpdatePlayStateHandler) { 
  25.  
  26.         __weak typeof(self) weakSelf = self; 
  27.  
  28.         [presenter setDidUpdatePlayStateHandler:^(NSUInteger playState) { 
  29.  
  30.             switch (playState) { 
  31.  
  32.                 case Buffering: weakSelf.playButton... break; 
  33.  
  34.                 case Playing: weakSelf.playButton... break; 
  35.  
  36.                 case Paused: weakSelf.playButton... break; 
  37.  
  38.             } 
  39.  
  40.         }]; 
  41.  
  42.     } 
  43.  
  44.  

播放的时候, VC只需要保持一下CellPresenter, 然后传入相应的playState调用didUpdatePlayStateHandler就可以更新Cell的状态了.

当然, 如果是Protocol的方式进行的VP绑定, 那么做这些事情就很平常了, 就不写了.

MVP大概就是这个样子了, 相对于MVC, 它其实只做了一件事情, 即分割业务展示和业务逻辑. 展示和逻辑分开后, 只要我们能保证V在收到P的数据更新通知后能正常刷新页面, 那么整个业务就没有问题. 因为V收到的通知其实都是来自于P层的数据获取/更新操作, 所以我们只要保证P层的这些操作都是正常的就可以了. 即我们只用测试P层的逻辑, 不必关心V层的情况.

  • MVVM

MVP其实已经是一个很好的架构, 几乎解决了所有已知的问题, 那么为什么还会有MVVM呢?

仍然是举例说明, 假设现在有一个Cell, 点击Cell上面的关注按钮可以是加关注, 也可以是取消关注, 在取消关注时, SceneA要求先弹窗询问, 而SceneB则不做弹窗, 那么此时的取消关注操作就和业务场景强关联, 所以这个接口不可能是V层直接调用, 会上升到Scene层.具体到代码中, 大概这个样子:

  1. @interface UserCellPresenter : NSObject 
  2.  
  3.   
  4.  
  5. @property (copy, nonatomic) void(^followStateHander)(BOOL isFollowing); 
  6.  
  7. @property (assign, nonatomic) BOOL isFollowing; 
  8.  
  9.   
  10.  
  11. - (void)follow; 
  12.  
  13. @end 
  14.  
  15.  
  16. @implementation UserCellPresenter 
  17.  
  18.   
  19.  
  20. - (void)follow { 
  21.  
  22.     if (!self.isFollowing) {//未关注 去关注 
  23.  
  24. //        follow user 
  25.  
  26.     } else {//已关注 则取消关注 
  27.  
  28.   
  29.  
  30.         self.followStateHander ? self.followStateHander(YES) : nil;//先通知Cell显示follow状态 
  31.  
  32.         [[FollowAPIManager new] unfollowWithUserId:self.userId completionHandler:^(NSError *error, id result) { 
  33.  
  34.             if (error) { 
  35.  
  36.                 self.followStateHander ? self.followStateHander(NO) : nil;//follow失败 状态回退 
  37.  
  38.             } eles { 
  39.  
  40.                 self.isFollowing = YES; 
  41.  
  42.             } 
  43.  
  44.             //...略 
  45.  
  46.         }]; 
  47.  
  48.     } 
  49.  
  50.  
  51. @end 
  52.  
  53.  
  54. @implementation UserCell 
  55.  
  56.   
  57.  
  58. - (void)setPresenter:(UserCellPresenter *)presenter { 
  59.  
  60.     _presenter = presenter; 
  61.  
  62.   
  63.  
  64.     if (!_presenter.followStateHander) { 
  65.  
  66.         __weak typeof(self) weakSelf = self; 
  67.  
  68.         [_presenter setFollowStateHander:^(BOOL isFollowing) { 
  69.  
  70.             [weakSelf.followStateButton setImage:isFollowing ? : ...]; 
  71.  
  72.         }]; 
  73.  
  74.     } 
  75.  
  76.  
  77.   
  78.  
  79. - (void)onClickFollowButton:(UIButton *)button {//将关注按钮点击事件上传 
  80.  
  81.     [self routeEvent:@"followEvent" userInfo:@{@"presenter" : self.presenter}]; 
  82.  
  83.  
  84.   
  85.  
  86. @end 
  87.  
  88.  
  89. @implementation FollowListViewController 
  90.  
  91.   
  92.  
  93. //拦截点击事件 判断后确认是否执行事件 
  94.  
  95. - (void)routeEvent:(NSString *)eventName userInfo:(NSDictionary *)userInfo { 
  96.  
  97.   
  98.  
  99.     if ([eventName isEqualToString:@"followEvent"]) { 
  100.  
  101.         UserCellPresenter *presenter = userInfo[@"presenter"]; 
  102.  
  103.         [self showAlertWithTitle:@"提示" message:@"确认取消对他的关注吗?" cancelHandler:nil confirmHandler: ^{ 
  104.  
  105.             [presenter follow]; 
  106.  
  107.         }]; 
  108.  
  109.     } 
  110.  
  111.  
  112.   
  113.  
  114. @end 
  115.  
  116.  
  117. @implementation UIResponder (Router) 
  118.  
  119.   
  120.  
  121. //沿着响应者链将事件上传 事件最终被拦截处理 或者 无人处理直接丢弃 
  122.  
  123. - (void)routeEvent:(NSString *)eventName userInfo:(NSDictionary *)userInfo { 
  124.  
  125.     [self.nextResponder routeEvent:eventName userInfo:userInfo]; 
  126.  
  127.  
  128. @end  

Block方式看起来略显繁琐, 我们换到Protocol看看:

  1. @protocol UserCellPresenterCallBack 
  2.  
  3.   
  4.  
  5. - (void)userCellPresenterDidUpdateFollowState:(BOOL)isFollowing; 
  6.  
  7.   
  8.  
  9. @end 
  10.  
  11.   
  12.  
  13. @interface UserCellPresenter : NSObject 
  14.  
  15.   
  16.  
  17. @property (weak, nonatomic) id view
  18.  
  19. @property (assign, nonatomic) BOOL isFollowing; 
  20.  
  21.   
  22.  
  23. - (void)follow; 
  24.  
  25.   
  26.  
  27. @end 
  28.  
  29.  
  30. @implementation UserCellPresenter 
  31.  
  32.   
  33.  
  34. - (void)follow { 
  35.  
  36.     if (!self.isFollowing) {//未关注 去关注 
  37.  
  38. //        follow user 
  39.  
  40.     } else {//已关注 则取消关注 
  41.  
  42.   
  43.  
  44.         BOOL isResponse = [self.view respondsToSelector:@selector(userCellPresenterDidUpdateFollowState)]; 
  45.  
  46.         isResponse ? [self.view userCellPresenterDidUpdateFollowState:YES] : nil; 
  47.  
  48.         [[FollowAPIManager new] unfollowWithUserId:self.userId completionHandler:^(NSError *error, id result) { 
  49.  
  50.             if (error) { 
  51.  
  52.                 isResponse ? [self.view userCellPresenterDidUpdateFollowState:NO] : nil; 
  53.  
  54.             } eles { 
  55.  
  56.                 self.isFollowing = YES; 
  57.  
  58.             } 
  59.  
  60.             //...略 
  61.  
  62.         }]; 
  63.  
  64.     } 
  65.  
  66.  
  67. @end 
  68.  
  69.  
  70. @implementation UserCell 
  71.  
  72.   
  73.  
  74. - (void)setPresenter:(UserCellPresenter *)presenter { 
  75.  
  76.   
  77.  
  78.     _presenter = presenter; 
  79.  
  80.     _presenter.view = self; 
  81.  
  82.  
  83.   
  84.  
  85. #pragma mark - UserCellPresenterCallBack 
  86.  
  87.   
  88.  
  89. - (void)userCellPresenterDidUpdateFollowState:(BOOL)isFollowing { 
  90.  
  91.     [self.followStateButton setImage:isFollowing ? : ...]; 
  92.  
  93.  

除去Route和VC中Alert之类的代码, 可以发现无论是Block方式还是Protocol方式因为需要对页面展示和业务逻辑进行隔离, 代码上饶了一小圈, 无形中增添了不少的代码量, 这里仅仅只是一个事件就这样, 如果是多个呢? 那写起来真是蛮伤的…

仔细看一下上面的代码就会发现, 如果我们继续添加事件, 那么大部分的代码都是在做一件事情: P层将数据更新通知到V层. Block方式会在P层添加很多属性, 在V层添加很多设置Block逻辑. 而Protocol方式虽然P层只添加了一个属性, 但是Protocol里面的方法却会一直增加, 对应的V层也就需要增加的方法实现.

问题既然找到了, 那就试着去解决一下吧, OC中能够实现两个对象间的低耦合通信, 除了Block和Protocol, 一般都会想到KVO. 我们看看KVO在上面的例子有何表现:

  1. @interface UserCellViewModel : NSObject 
  2.  
  3.   
  4.  
  5. @property (assign, nonatomic) BOOL isFollowing; 
  6.  
  7.   
  8.  
  9. - (void)follow; 
  10.  
  11. @end 
  12.  
  13.  
  14. @implementation UserCellViewModel 
  15.  
  16.   
  17.  
  18. - (void)follow { 
  19.  
  20.     if (!self.isFollowing) {//未关注 去关注 
  21.  
  22. //        follow user 
  23.  
  24.     } else {//已关注 则取消关注 
  25.  
  26.   
  27.  
  28.         self.isFollowing = YES;//先通知Cell显示follow状态 
  29.  
  30.         [[FollowAPIManager new] unfollowWithUserId:self.userId completionHandler:^(NSError *error, id result) { 
  31.  
  32.             if (error) { self.isFollowing = NO; }//follow失败 状态回退 
  33.  
  34.             //...略 
  35.  
  36.         }]; 
  37.  
  38.     } 
  39.  
  40.  
  41. @end 
  42.  
  43.  
  44. @implementation UserCell 
  45.  
  46. - (void)awakeFromNib { 
  47.  
  48.     @weakify(self); 
  49.  
  50.     [RACObserve(self, viewModel.isFollowing) subscribeNext:^(NSNumber *isFollowing) { 
  51.  
  52.         @strongify(self); 
  53.  
  54.         [self.followStateButton setImage:[isFollowing boolValue] ? : ...]; 
  55.  
  56.     }; 
  57.  
  58.  

代码大概少了一半左右, 另外, 逻辑读起来也清晰多了, Cell观察绑定的ViewModel的isFollowing状态, 并在状态改变时, 更新自己的展示.

三种数据通知方式简单一比对, 相信哪种方式对程序员更加友好, 大家都心里有数, 就不做赘述了.

现在大概一提到MVVM就会想到RAC, 但这两者其实并没有什么联系, 对于MVVM而言RAC只是提供了优雅安全的数据绑定方式, 如果不想学RAC, 自己搞个KVOHelper之类的东西也是可以的. 另外 ,RAC的魅力其实在于函数式响应式编程, 我们不应该仅仅将它局限于MVVM的应用, 日常的开发中也应该多使用使用的.

关于MVVM, 我想说的就是这么多了, 因为MVVM其实只是MVP的绑定进化体, 除去数据绑定方式, 其他的和MVP如出一辙, 只是可能呈现方式是Command/Signal而不是CompletionHandler之类的, 故不做赘述.

最后做个简单的总结吧:

1.MVC作为老牌架构, 优点在于将业务场景按展示数据类型划分出多个模块, 每个模块中的C层负责业务逻辑和业务展示, 而M和V应该是互相隔离的以做重用, 另外每个模块处理得当也可以作为重用单元. 拆分在于解耦, 顺便做了减负, 隔离在于重用, 提升开发效率. 缺点是没有区分业务逻辑和业务展示, 对单元测试不友好.

2.MVP作为MVC的进阶版, 提出区分业务逻辑和业务展示, 将所有的业务逻辑转移到P层, V层接受P层的数据更新通知进行页面展示. 优点在于良好的分层带来了友好的单元测试, 缺点在于分层会让代码逻辑优点绕, 同时也带来了大量的代码工作, 对程序员不够友好.

3.MVVM作为集大成者, 通过数据绑定做数据更新, 减少了大量的代码工作, 同时优化了代码逻辑, 只是学习成本有点高, 对新手不够友好.

4.MVP和MVVM因为分层所以会建立MVC两倍以上的文件类, 需要良好的代码管理方式.

5.在MVP和MVVM中, V和P或者VM之间理论上是多对多的关系, 不同的布局在相同的逻辑下只需要替换V层, 而相同的布局不同的逻辑只需要替换P或者VM层. 但实际开发中P或者VM往往因为耦合了V层的展示逻辑退化成了一对一关系(比如SceneA中需要显示”xxx+Name”, VM就将Name格式化为”xxx + Name”. 某一天SceneB也用到这个模块, 所有的点击事件和页面展示都一样, 只是Name展示为”yyy + Name”, 此时的VM因为耦合SceneA的展示逻辑, 就显得比较尴尬), 针对此类情况, 通常有两种办法, 一种是在VM层加状态进而判断输出状态, 一种是在VM层外再加一层FormatHelper. 前者可能因为状态过多显得代码难看, 后者虽然比较优雅且拓展性高, 但是过多的分层在数据还原时就略显笨拙, 大家应该按需选择.

这里随便瞎扯一句, 有些文章上来就说MVVM是为了解决C层臃肿, MVC难以测试的问题, 其实并不是这样的. 按照架构演进顺序来看, C层臃肿大部分是没有拆分好MVC模块, 好好拆分就行了, 用不着MVVM. 而MVC难以测试也可以用MVP来解决, 只是MVP也并非完美, 在VP之间的数据交互太繁琐, 所以才引出了MVVM. 当MVVM这个完全体出现以后, 我们从结果看起源, 发现它做了好多事情, 其实并不是, 它的前辈们付出的努力也并不少!

  • 架构那么多, 日常开发中到底该如何选择?

不管是MVC, MVP, MVVM还是MVXXX, 最终的目的在于服务于人, 我们注重架构, 注重分层都是为了开发效率, 说到底还是为了开心. 所以, 在实际开发中不应该拘泥于某一种架构, 根据实际项目出发, 一般普通的MVC就能应对大部分的开发需求, 至于MVP和MVVM, 可以尝试, 但不要强制.

总之, 希望大家能做到: 设计时, 心中有数. 撸码时, 开心就好.

【编辑推荐】

  1. 谈谈MVVM和链式网络请求架构
  2. 如何构建Android MVVM应用框架
  3. 不必纠结MVC还是MVP了,听我说两句~
  4. 如何构建Android MVVM应用程序
  5. 杂谈: MVC/MVP/MVVM (一)
【责任编辑:枯木 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

《ASP.NET AJAX Web 应用开发秘诀(VB版)》

本书详细介绍了AJAX在Web开发上的应用。主要内容包括:ASP.NET AJAX技术概述、实现异步局部更新页面、UpdatePanel编程功能、PageRequestMan...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊
× 学习达标赢Beats耳机