精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

雜談: MVC/MVP/MVVM (二)

移動開發 iOS
本文為回答一位朋友關于MVC/MVP/MVVM架構方面的疑問所寫, 旨在介紹iOS下MVC/MVP/MVVM三種架構的設計思路以及各自的優缺點。
  • 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, 可以嘗試, 但不要強制.

總之, 希望大家能做到: 設計時, 心中有數. 擼碼時, 開心就好. 

責任編輯:龐桂玉 來源: iOS大全
相關推薦

2017-03-31 20:45:41

MVCMVPMVVM

2018-03-21 16:19:40

MVCMVPMVVM

2014-03-17 11:05:00

ScriptCode Blocks

2019-09-11 08:52:24

MVCMVPMVVM

2019-10-30 14:58:45

MVCAndroid表現層

2023-04-11 07:50:27

軟件架構設計

2018-10-29 11:41:22

架構MVCAndroid

2023-10-20 13:21:55

軟件設計模式架構

2017-05-12 16:50:14

GUI應用程序

2015-12-04 09:33:15

程序員前端演進史

2009-04-30 15:56:50

三層架構MVCMVP

2009-07-24 13:54:39

MVVM模式

2012-05-28 10:34:50

MVVM 數據綁定

2017-02-24 14:05:14

AndroidMVCMVP

2009-12-21 09:22:51

SilverlightMVVM模式

2018-07-10 10:00:15

Android架構MVC

2010-01-27 08:44:56

ASP.NET MVC

2016-03-30 09:34:27

2015-09-29 10:07:58

中文編碼

2012-11-05 14:57:50

MVP
點贊
收藏

51CTO技術棧公眾號

国产精品久久久久久久7电影| 日韩欧美亚洲一区二区| 日韩欧美在线一区二区| 中文字幕一区二区在线视频| 午夜久久免费观看| 亚洲成年人在线播放| 虎白女粉嫩尤物福利视频| 免费在线黄色电影| 韩国三级在线一区| 97欧美精品一区二区三区| 91视频免费观看网站| 午夜精品久久久久久毛片| 亚洲午夜久久久久久久久久久| 乱色588欧美| 国产高清免费观看| 日本伊人午夜精品| 高清亚洲成在人网站天堂| 极品蜜桃臀肥臀-x88av| 一区二区三区四区高清视频| 在线免费av一区| 欧美一级欧美一级| 日本在线观看www| 99精品视频在线免费观看| 91在线观看免费| 一级片免费在线播放| 国产精品a级| 久久激情五月丁香伊人| 丰满少妇一区二区三区| 深夜激情久久| 欧美精品欧美精品系列| 精品少妇无遮挡毛片| av在线理伦电影| 国产色一区二区| 好吊色欧美一区二区三区四区 | 欧美高清不卡| 中文字幕日韩欧美在线| 97人妻天天摸天天爽天天| 精品久久久久久久久久岛国gif| 色婷婷亚洲精品| 国产二区视频在线播放| 男插女视频久久久| 亚洲六月丁香色婷婷综合久久| 日本精品一区二区三区不卡无字幕| 黑人精品一区二区| 国产成人高清在线| 91香蕉国产在线观看| 中文字幕视频免费观看| 三级成人在线视频| 日韩av毛片网| 精品免费囯产一区二区三区| 中文国产一区| 国产69精品久久久| 精品无码久久久久| 亚洲无线视频| 国外成人在线视频| 国产精品变态另类虐交| 激情视频一区| 97久久精品视频| 91玉足脚交嫩脚丫在线播放| 亚洲影音先锋| 日本精品久久久久影院| 欧美日韩一级黄色片| 久久久久久夜| 国产精品美女在线| 一二三四区视频| 韩国午夜理伦三级不卡影院| 96久久精品| 亚洲精品视频网| 91在线观看污| 天天综合狠狠精品| 黄色动漫在线观看| 亚洲精品日日夜夜| 青青青在线视频播放| 成人欧美一区二区三区的电影| 欧美日韩亚洲一区二区三区| 久热免费在线观看| 精品91久久| 欧美日韩精品一区二区| 红桃视频一区二区三区免费| 成人自拍在线| 亚洲欧美福利视频| 亚洲欧洲综合网| 国内自拍视频一区二区三区| 8x拔播拔播x8国产精品| 中文字幕 国产精品| 狠狠色丁香久久婷婷综合丁香| 亚洲淫片在线视频| 亚洲日本香蕉视频| 欧美极品美女视频| 嫩草影院中文字幕| 91精品产国品一二三产区| 在线亚洲+欧美+日本专区| 在线视频观看一区二区| 国产精品毛片视频| 丝袜美腿亚洲一区二区| 精品一区在线视频| 日本在线播放一区二区三区| 产国精品偷在线| 九色在线播放| 亚洲一区二区综合| 国产一级特黄a大片免费| 蜜桃精品视频| 国产偷国产偷亚洲清高网站 | 一区二区成人在线| 天天摸天天碰天天添| 国产精品**亚洲精品| 亚洲精品在线看| 日本黄色小说视频| 久久精品电影| 国产精品污www一区二区三区| 国产在线高清| 亚洲成在人线免费| 日韩高清在线一区二区| 免费成人av| 欧美高清自拍一区| 在线视频播放大全| ww久久中文字幕| 国产黄色激情视频| 欧美亚洲黄色| 亚洲视频免费一区| 久久露脸国语精品国产91| 国产在线视频一区二区| 日本在线观看一区| av中文在线资源库| 日韩欧美国产成人一区二区| 五月天婷婷丁香网| 久久精品一区二区三区中文字幕| 国产精品麻豆免费版| 国产在线观看a| 精品污污网站免费看| 精品人妻少妇嫩草av无码| 狠狠88综合久久久久综合网| 91精品久久久久久久久不口人| 日韩三级电影网| 午夜久久久久久电影| 欧美性猛交xx| 亚洲乱码精品| 成人免费高清完整版在线观看| 欧美男男同志| 日本久久一区二区三区| 亚洲欧美视频在线播放| 午夜在线精品偷拍| 久久综合久久综合这里只有精品| 国产欧洲在线| 亚洲国产欧美在线成人app| 精品无码人妻一区二区三区品| 国产精品一区二区在线看| 欧洲美女和动交zoz0z| 99tv成人影院| 萌白酱国产一区二区| 国产又粗又猛又爽又黄的视频一| 国产精品国产三级国产| 中文字幕 欧美日韩| 日韩成人激情| 成人做爽爽免费视频| 久久日韩视频| 日韩午夜激情视频| 久久久久性色av无码一区二区| 国产福利一区在线观看| 国产一区 在线播放| 成人直播在线观看| 91精品国产亚洲| 免费毛片在线| 欧美制服丝袜第一页| 久久久久久国产免费a片| 麻豆精品在线播放| 神马午夜伦理影院| 粉嫩av一区二区| 人九九综合九九宗合| 岛国最新视频免费在线观看| 欧美日韩精品福利| 欧美交换国产一区内射| av影院午夜一区| 久久精品免费网站| 亚洲精品小说| 精品中文字幕一区| 精品肉辣文txt下载| 久久综合88中文色鬼| 国产 欧美 自拍| 色激情天天射综合网| 韩国一级黄色录像| 成人av网址在线| 亚洲老女人av| 国语精品一区| 日韩av不卡播放| 国内精品视频| 欧美在线观看日本一区| 日本在线免费| 亚洲国产成人精品久久久国产成人一区| 国产精品人人人人| 亚洲欧洲一区二区在线播放| 婷婷五月精品中文字幕| 久久婷婷亚洲| 欧美日韩中文字幕在线播放| 伊人久久大香线蕉| 亚洲综合精品一区二区| 久久uomeier| 美女少妇精品视频| 成人在线免费电影| 亚洲变态欧美另类捆绑| 在线免费观看高清视频| 欧美日韩国产中文精品字幕自在自线| 波多野结衣喷潮| 久久伊人蜜桃av一区二区| 久久精品视频在线观看免费| 亚洲一区成人| 久久久久久久久久伊人| 精品日韩毛片| 久久精品ww人人做人人爽| 警花av一区二区三区| 欧美重口另类videos人妖| 91麻豆一二三四在线| 在线色欧美三级视频| 天天干天天色天天| 宅男噜噜噜66一区二区66| 精品人妻一区二区三区潮喷在线 | av免费观看国产| 国产精品99在线观看| 欧美日韩日本网| av成人app永久免费| 91九色视频导航| 国产电影一区二区三区爱妃记| 51久久精品夜色国产麻豆| 欧洲中文在线| 久久99热精品| 久久77777| 日韩在线视频二区| www亚洲人| 亚洲天堂网站在线观看视频| 色婷婷av一区二区三| 精品久久久影院| 亚洲精品无遮挡| 日韩美一区二区三区| 国产男男gay体育生白袜| 欧美日韩视频在线第一区| 日韩人妻精品中文字幕| 狠狠做深爱婷婷久久综合一区| 豆国产97在线 | 亚洲| 亚洲资源在线观看| 黄页网站免费观看| 亚洲一区在线播放| 久久精品久久精品久久| 亚洲一二三区不卡| 久久影院一区二区| 亚洲国产精品嫩草影院| 免费人成视频在线| 五月综合激情日本mⅴ| 国语对白一区二区| 性做久久久久久| 91午夜视频在线观看| 精品成人国产在线观看男人呻吟| 国产精品成人aaaa在线| 亚洲va在线va天堂| 日韩欧美激情视频| 欧美日韩国产中文字幕 | 麻豆成人av| 欧美日韩播放| 亚洲综合av一区| 一区二区蜜桃| 久操网在线观看| 国产日韩一区| 香港日本韩国三级网站| 美女一区二区久久| 日本成人在线免费| 99久久综合精品| 国产黄色录像视频| 亚洲欧美一区二区久久| 久久视频免费看| 欧美日韩激情美女| 一级黄色短视频| 日韩欧美成人激情| 欧美巨乳在线| 日韩一区在线视频| 成年网站在线视频网站| 欧美中文字幕精品| 高清不卡一区| 狠狠干一区二区| 日本不卡高清| 中文精品无码中文字幕无码专区 | 久久国产激情视频| 国产精品性做久久久久久| 在线观看国产三级| 中文字幕不卡在线观看| 欧美国产日韩在线观看成人| 五月婷婷欧美视频| 亚洲无码精品国产| 精品国产免费久久| 国产裸舞福利在线视频合集| 粗暴蹂躏中文一区二区三区| 性国裸体高清亚洲| 成人精品在线视频| 小说区图片区色综合区| 久久久成人精品一区二区三区| 一本色道久久综合| 色18美女社区| 久久综合狠狠综合久久激情| 久久福利免费视频| 色综合天天综合网天天看片| 999久久久久久| 国产一区二区三区免费视频| 午夜影院免费在线| 国产精品欧美一区二区三区奶水| 99精品国产一区二区三区2021| 日韩av高清在线播放| 精品成人在线| 亚洲欧美一区二区三区不卡| 国产视频一区二区在线| 日韩欧美一区二区一幕| 欧美午夜一区二区三区免费大片| 神马午夜电影一区二区三区在线观看| 色视频www在线播放国产成人| 亚洲天堂手机| av成人观看| 外国成人激情视频| 美女喷白浆视频| 99麻豆久久久国产精品免费| 澳门黄色一级片| 在线成人免费观看| 国产大学生校花援交在线播放| 午夜精品理论片| 日韩免费成人| 91免费视频黄| 久久精品国产精品青草| 夜夜春很很躁夜夜躁| 动漫精品一区二区| 少妇一区二区三区四区| 欧美国产中文字幕| 国产精品va视频| 欧美 日韩 国产 在线观看| 蜜桃av噜噜一区二区三区小说| 成年人免费观看视频网站| 午夜亚洲福利老司机| 亚洲女同志亚洲女同女播放| 久久国产加勒比精品无码| 久久精品xxxxx| 亚洲国产精品视频一区| 日韩av电影一区| 久久亚洲AV无码专区成人国产| 欧美日韩另类在线| 无码国精品一区二区免费蜜桃| 久久男人资源视频| 国产精品传媒| 免费成人在线视频网站| av网站一区二区三区| 久久久精品免费看| 亚洲视频欧洲视频| 欧美色999| 亚洲欧美国产精品桃花| 美腿丝袜在线亚洲一区| 神马久久精品综合| 日韩视频在线一区二区| 日本在线视频www鲁啊鲁| 成人精品水蜜桃| 一区二区亚洲精品| 91黄色免费视频| 欧美视频在线观看 亚洲欧| 欧美熟妇交换久久久久久分类| 97精品国产aⅴ7777| 天堂在线精品| 成人性生生活性生交12| 国产精品国产三级国产aⅴ原创| 国产区精品在线| 久久久久久噜噜噜久久久精品| 欧美高清视频看片在线观看 | 精品一区二区三区在线视频| 亚洲熟女毛茸茸| 精品日韩成人av| 中文在线а√天堂| 亚洲欧洲日韩综合二区| 韩国成人福利片在线播放| 久久久久久久久精| 日韩精品在线免费| 91精品国产66| 久久综合亚洲精品| 久久综合九色综合欧美98| 一级片在线观看视频| 九九热最新视频//这里只有精品| 久久九九热re6这里有精品| 亚洲综合在线网站| 1区2区3区欧美| 熟妇人妻av无码一区二区三区| 国产精品成人国产乱一区| 亚洲成av人电影| 极品白嫩丰满美女无套| 欧美人妇做爰xxxⅹ性高电影| 丰满大乳少妇在线观看网站| 鲁丝片一区二区三区| 国产一区二区免费看| 欧美 日韩 精品| 欧美成人一区二区三区电影| 亚洲精品合集| 天天操夜夜操很很操| 色综合久久久久综合| 黄色av电影在线播放| 欧美精品亚洲| 国产成人av电影在线观看| 999视频在线| 国外成人在线直播| 亚洲天堂免费| 欧洲美熟女乱又伦|