Miscellaneous: MVC/MVP/MVVM (Part 2)

Miscellaneous: MVC/MVP/MVVM (Part 2)
  • MVP

The disadvantage of MVC is that it does not distinguish between business logic and business presentation, which is not friendly to unit testing. MVP has optimized the above disadvantages. It also isolates business logic and business presentation, and the corresponding one becomes MVCP. The functions of M and V remain unchanged, the original C is now only responsible for layout, and all logic has been transferred to the P layer.

The corresponding relationship is shown in the figure:

The business scenario has not changed. It still displays three types of data, but the three MVCs are replaced by three MVPs (I only drew the Blog module in the figure). UserVC is responsible for configuring the three MVPs (creating their own VPs, establishing C through VPs, and C is responsible for establishing the binding relationship between VPs), and notifying their own P layers (previously notifying the C layer) to obtain data at the appropriate time. After obtaining the data, each P layer will perform corresponding processing. After the processing is completed, it will notify the bound View that the data has been updated. After receiving the update notification, V obtains the formatted data from P for page rendering, and UserVC*** lays out the rendered Views. In addition, the V layer and the C layer no longer process any business logic, and all event triggers call the corresponding commands of the P layer. The specific code is as follows:

  1. @interface BlogPresenter : NSObject
  2.  
  3.   
  4.  
  5. + (instancetype)instanceWithUserId:(NSUInteger)userId;
  6.  
  7.   
  8.  
  9. - (NSArray *)allDatas; //Business logic is moved to the P layer and business-related M is also moved to the P layer
  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.  
  48. - (instancetype)initWithUserId:(NSUInteger)userId {
  49.  
  50. if (self = [super init]) {
  51.  
  52. self.userId = userId;
  53.  
  54. self.apiManager = [UserAPIManager new];
  55.  
  56. //...omitted
  57.  
  58. }
  59.  
  60. }
  61.  
  62.   
  63.  
  64. #pragma mark - Interface
  65.  
  66.   
  67.  
  68. - (NSArray *)allDatas {
  69.  
  70. return self.blogs;
  71.  
  72. }
  73.  
  74. //Commands provided to the outer layer
  75.  
  76. - (void)refreshUserBlogsWithCompletionHandler:(NetworkTaskCompletionHander)completionHandler {
  77.  
  78.   
  79.  
  80. [self.apiManager refreshUserBlogsWithUserId:self.userId completionHandler:^(NSError *error, id result) {
  81.  
  82. if (!error) {
  83.  
  84.   
  85.  
  86. [self.blogs removeAllObjects]; //Clear previous data
  87.  
  88. for (Blog *blog in result) {
  89.  
  90. [self.blogs addObject:[BlogCellPresenter presenterWithBlog:blog]];
  91.  
  92. }
  93.  
  94. }
  95.  
  96. completionHandler ? completionHandler(error, result) : nil;
  97.  
  98. }];
  99.  
  100. }
  101.  
  102. //Commands provided to the outer layer
  103.  
  104. - (void)loadMoreUserBlogsWithCompletionHandler:(NetworkTaskCompletionHander)completionHandler {
  105.  
  106. [self.apiManager loadMoreUserBlogsWithUserId:self.userId completionHandler...]
  107.  
  108. }
  109.  
  110.   
  111.  
  112. @ end  
  113.  
  114.  
  115. @interface BlogCellPresenter : NSObject
  116.  
  117.   
  118.  
  119. + (instancetype)presenterWithBlog:(Blog *)blog;
  120.  
  121.   
  122.  
  123. - (NSString *)authorText;
  124.  
  125. - (NSString *)likeCountText;
  126.  
  127.   
  128.  
  129. - (void)likeBlogWithCompletionHandler:(NetworkTaskCompletionHander)completionHandler;
  130.  
  131. - (void)shareBlogWithCompletionHandler:(NetworkTaskCompletionHander)completionHandler;
  132.  
  133. @ end  
  134.  
  135.  
  136. @implementation BlogCellPresenter
  137.  
  138.   
  139.  
  140. - (NSString *)likeCountText {
  141.  
  142. return [NSString stringWithFormat:@ "Like %ld" , self.blog.likeCount];
  143.  
  144. }
  145.  
  146.   
  147.  
  148. - (NSString *)authorText {
  149.  
  150. return [NSString stringWithFormat:@ "Author name: %@" , self.blog.authorName];
  151.  
  152. }
  153.  
  154. // ...omitted
  155.  
  156. - (void)likeBlogWithCompletionHandler:(NetworkTaskCompletionHander)completionHandler {
  157.  
  158.   
  159.  
  160. [[UserAPIManager new] likeBlogWithBlogId:self.blogId userId:self.userId completionHandler:^(NSError *error, id result) {
  161.  
  162. if (error) {
  163.  
  164. //do fail
  165.  
  166. } else {
  167.  
  168. //do success
  169.  
  170. self.blog.likeCount += 1;
  171.  
  172. }
  173.  
  174. completionHandler ? completionHandler(error, result) : nil;
  175.  
  176. }];
  177.  
  178. }
  179.  
  180. // ...omitted
  181.  
  182. @ end  

BlogPresenter and BlogCellPresenter are the P layers of BlogViewController and BlogCell respectively, which are actually a collection of business logic. BlogPresenter is responsible for obtaining the original data of Blogs and constructing BlogCellPresenter based on these original data, while BlogCellPresenter provides various formatted data for Cell rendering. In addition, the business of likes and sharing is now also transferred here.

The business logic has been moved to the P layer, and the V layer only needs to do two things:

1. Listen to the data update notification of the P layer and refresh the page display.

2. When the click event is triggered, the corresponding method of the P layer is called and the method execution result is displayed.

  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. //Get formatted data from Presenter for display
  17.  
  18. self.authorLabel.text = presenter.authorText;
  19.  
  20. self.likeCountLebel.text = presenter.likeCountText;
  21.  
  22. // ...omitted
  23.  
  24. }
  25.  
  26.   
  27.  
  28. #pragma mark - Action  
  29.  
  30.   
  31.  
  32. - (void)onClickLikeButton:(UIButton *)sender {
  33.  
  34. [self.presenter likeBlogWithCompletionHandler:^(NSError *error, id result) {
  35.  
  36. if (!error) {//page refresh
  37.  
  38. self.likeCountLebel.text = self.presenter.likeCountText;
  39.  
  40. }
  41.  
  42. // ...omitted
  43.  
  44. }];
  45.  
  46. }
  47.  
  48.   
  49.  
  50. @ end  

What the C layer does is to bind the layout to the PV (it may not be obvious here, because the layout code in BlogVC is TableViewDataSource, and the PV binding is not obvious because I was lazy and used Block for notification callback. If it is a Protocol callback, it will be very obvious). The code is as follows:

  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 is no longer responsible for the actual data acquisition logic. Data acquisition directly calls the corresponding interface of Presenter. In addition, because the business logic has also been transferred to Presenter, the layout of TableView also uses Presenter.allDatas. As for the display of Cell, we replaced a large number of the original Set methods and let Cell display itself according to the bound CellPresenter. After all, the logic has been moved to the P layer. The V layer must rely on the corresponding P layer commands for corresponding interactions. Fortunately, V and M are still isolated, but coupled with P. The P layer can be replaced at will, but M obviously cannot. This is a compromise.

*** is Scene, which has not changed much, except that the configuration of MVC is replaced by MVP. In addition, data acquisition also goes through the P layer instead of the C layer (however, this is not the case in the code):

  1. - (void)configuration {
  2.  
  3.   
  4.  
  5. // ...other settings
  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. // ...omitted
  18.  
  19. }
  20.  
  21.   
  22.  
  23. - (void)fetchData {
  24.  
  25.   
  26.  
  27. // ...omitted
  28.  
  29. [self.userInfoVC fetchData];
  30.  
  31. [HUD show];
  32.  
  33. [self.blogViewController fetchDataWithCompletionHandler:^(NSError *error, id result) {
  34.  
  35. [HUD hide];
  36.  
  37. }];
  38.  
  39. // Still because of laziness, using Block to forward on the C layer will save some code. If it is Protocol or KVO, self.blogViewController.presenter will be used
  40.  
  41. //But it doesn't matter, because we replaced MVC with MVP to solve the problem of unit testing. The current usage does not affect unit testing at all, it just does not match the concept.
  42.  
  43. // ...omitted
  44.  
  45. }

There is actually a problem in the above example, that is, we assume that all events are initiated by the V layer and are one-time. This is actually not true. Let's take a simple example: on a page like WeChat voice chat, click on the voice Cell to start playing, the Cell displays the playing animation, the animation stops when the playing is completed, and then the next voice is played.

In this playback scenario, if CellPresenter still only provides a playWithCompletionHandler interface as above, it will not work. Because the callback after the playback is completed must be in the C layer. After the playback is completed, the C layer will find that the CellPresenter that executes the playback command at this time cannot notify the Cell to stop the animation, that is, the event triggering is not a one-time event. In addition, after the playback is completed, when the C layer traverses to the next CellPresenterX to be played and calls the playback interface, CellPresenterX does not know who the corresponding Cell is, and of course it cannot notify the Cell to start the animation, that is, the initiator of the event is not necessarily the V layer.

For these non-one-time or other layer initiated events, the processing method is actually very simple, just add a Block attribute to CellPresenter. Because it is an attribute, Block can be called back multiple times. In addition, Block can also capture Cell, so there is no need to worry about not finding the corresponding Cell. It's like this:

  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. }

When playing, VC only needs to keep CellPresenter, and then pass in the corresponding playState to call didUpdatePlayStateHandler to update the state of Cell.

Of course, if the VP binding is done by Protocol, then doing these things is very common and will not be described here.

This is what MVP looks like. Compared with MVC, it actually only does one thing, which is to separate business display and business logic. After display and logic are separated, as long as we can ensure that V can refresh the page normally after receiving data update notification from P, then the entire business will be fine. Because the notifications received by V actually come from the data acquisition/update operations of the P layer, so we only need to ensure that these operations of the P layer are normal. That is, we only need to test the logic of the P layer and don't need to care about the situation of the V layer.

  • MVVM

MVP is actually a very good architecture that solves almost all known problems, so why is there MVVM?

Still using an example, suppose there is a Cell now. Clicking the Follow button on the Cell can be followed or unfollowed. When unfollowing, SceneA requires a pop-up window to ask first, while SceneB does not make a pop-up window. In this case, the unfollow operation is strongly related to the business scenario, so this interface cannot be called directly by the V layer, but will rise to the Scene layer. Specifically in the code, it looks like this:

  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) {//Not following Go to follow
  23.  
  24. // follow user  
  25.  
  26. } else {//Already followed, then unfollow
  27.  
  28.   
  29.  
  30. self.followStateHander ? self.followStateHander(YES) : nil; //First notify the Cell to display the follow status
  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 failed, state fallback
  37.  
  38. }eles {
  39.  
  40. self.isFollowing = YES;
  41.  
  42. }
  43.  
  44. //...omitted
  45.  
  46. }];
  47.  
  48. }
  49.  
  50. }
  51.  
  52. @ end  
  53.  
  54.  
  55. @implementation UserCell
  56.  
  57.   
  58.  
  59. - (void)setPresenter:(UserCellPresenter *)presenter {
  60.  
  61. _presenter = presenter;
  62.  
  63.   
  64.  
  65. if (!_presenter.followStateHander) {
  66.  
  67. __weak typeof(self) weakSelf = self;
  68.  
  69. [_presenter setFollowStateHander:^(BOOL isFollowing) {
  70.  
  71. [weakSelf.followStateButton setImage:isFollowing ? : ...];
  72.  
  73. }];
  74.  
  75. }
  76.  
  77. }
  78.  
  79.   
  80.  
  81. - (void)onClickFollowButton:(UIButton *)button {//Upload the follow button click event
  82.  
  83. [self routeEvent:@ "followEvent" userInfo:@{@ "presenter" : self.presenter}];
  84.  
  85. }
  86.  
  87.   
  88.  
  89. @ end  
  90.  
  91.  
  92. @implementation FollowListViewController
  93.  
  94.   
  95.  
  96. //Intercept click events and confirm whether to execute the event
  97.  
  98. - (void)routeEvent:(NSString *)eventName userInfo:(NSDictionary *)userInfo {
  99.  
  100.   
  101.  
  102. if ([eventName isEqualToString:@ "followEvent" ]) {
  103.  
  104. UserCellPresenter *presenter = userInfo[@ "presenter" ];
  105.  
  106. [self showAlertWithTitle:@ "Prompt" message:@ "Are you sure you want to cancel your attention to him?" cancelHandler:nil confirmHandler: ^{
  107.  
  108. [presenter follow];
  109.  
  110. }];
  111.  
  112. }
  113.  
  114. }
  115.  
  116.   
  117.  
  118. @ end  
  119.  
  120.  
  121. @implementation UIResponder (Router)
  122.  
  123.   
  124.  
  125. //Upload the event along the responder chain. The event is eventually intercepted and processed or discarded without processing.
  126.  
  127. - (void)routeEvent:(NSString *)eventName userInfo:(NSDictionary *)userInfo {
  128.  
  129. [self.nextResponder routeEvent:eventName userInfo:userInfo];
  130.  
  131. }
  132.  
  133. @ end  

The Block method seems a bit cumbersome, so let's switch to the Protocol method:

  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) {//Not following Go to follow
  37.  
  38. // follow user  
  39.  
  40. } else {//Already followed, then unfollow
  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. //...omitted
  61.  
  62. }];
  63.  
  64. }
  65.  
  66. }
  67.  
  68. @ end  
  69.  
  70.  
  71. @implementation UserCell
  72.  
  73.   
  74.  
  75. - (void)setPresenter:(UserCellPresenter *)presenter {
  76.  
  77.   
  78.  
  79. _presenter = presenter;
  80.  
  81. _presenter.view = self;
  82.  
  83. }
  84.  
  85.   
  86.  
  87. #pragma mark - UserCellPresenterCallBack
  88.  
  89.   
  90.  
  91. - (void)userCellPresenterDidUpdateFollowState:(BOOL)isFollowing {
  92.  
  93. [self.followStateButton setImage:isFollowing ? : ...];
  94.  
  95. }

Excluding the codes like Route and Alert in VC, we can find that both Block and Protocol methods need to isolate page display and business logic, so the code goes around a little bit, which invisibly increases the amount of code. This is just one event, what if there are multiple events? It would be really difficult to write...

If you look at the above code carefully, you will find that if we continue to add events, most of the code is doing one thing: the P layer notifies the V layer of data updates. The Block method will add many properties to the P layer and many setting Block logic to the V layer. Although the Protocol method only adds one property to the P layer, the methods in the Protocol will continue to increase, and the corresponding V layer will also need to add methods to implement.

Now that the problem has been found, let's try to solve it. In OC, low-coupling communication between two objects can be achieved. In addition to Block and Protocol, KVO is generally considered. Let's see how KVO performs in the above example:

  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) {//Not following Go to follow
  21.  
  22. // follow user  
  23.  
  24. } else {//Already followed, then unfollow
  25.  
  26.   
  27.  
  28. self.isFollowing = YES; //First notify the Cell to display the follow status
  29.  
  30. [[FollowAPIManager new] unfollowWithUserId:self.userId completionHandler:^(NSError *error, id result) {
  31.  
  32. if (error) { self.isFollowing = NO ; } //follow failed, status fallback
  33.  
  34. //...omitted
  35.  
  36. }];
  37.  
  38. }
  39.  
  40. }
  41.  
  42. @ end  
  43.  
  44.  
  45. @implementation UserCell
  46.  
  47. - (void)awakeFromNib {
  48.  
  49. @weakify(self);
  50.  
  51. [RACObserve(self, viewModel.isFollowing) subscribeNext:^(NSNumber *isFollowing) {
  52.  
  53. @strongify(self);
  54.  
  55. [self.followStateButton setImage:[isFollowing boolValue] ? : ...];
  56.  
  57. };
  58.  
  59. }

The code is about half as long as it should be. In addition, the logic is much clearer to read. The Cell observes the isFollowing state of the bound ViewModel and updates its display when the state changes.

A simple comparison of the three data notification methods shows that everyone knows which method is more programmer-friendly, so I won't go into details.

Now, when MVVM is mentioned, RAC will probably come to mind, but there is actually no connection between the two. For MVVM, RAC only provides an elegant and safe way of data binding. If you don't want to learn RAC, you can also make something like KVOHelper yourself. In addition, the charm of RAC actually lies in functional responsive programming. We should not limit it to the application of MVVM, but also use it more in daily development.

That's all I want to say about MVVM, because MVVM is actually just a binding evolution of MVP. Except for the data binding method, everything else is exactly the same as MVP, except that the presentation method may be Command/Signal instead of CompletionHandler, so I won't go into details.

*** Let's make a brief summary:

1. MVC is an old-fashioned architecture. Its advantage is that it divides business scenarios into multiple modules according to the display data type. The C layer in each module is responsible for business logic and business display, while M and V should be isolated from each other for reuse. In addition, each module can also be used as a reuse unit if it is properly handled. Splitting is about decoupling and reducing the burden. Isolation is about reuse and improving development efficiency. The disadvantage is that it does not distinguish between business logic and business display, which is not friendly to unit testing.

2. MVP, as an advanced version of MVC, proposes to distinguish business logic from business display, transfer all business logic to the P layer, and the V layer receives data update notifications from the P layer for page display. The advantage is that good stratification brings friendly unit testing, but the disadvantage is that stratification makes the code logic complicated and also brings a lot of code work, which is not friendly to programmers.

3. MVVM, as a master, updates data through data binding, reduces a lot of code work, and optimizes code logic. However, the learning cost is a bit high and it is not friendly to novices.

4. MVP and MVVM will create more than twice the file classes of MVC because of their layering, which requires good code management.

5. In MVP and MVVM, the relationship between V and P or VM is theoretically many-to-many. Different layouts under the same logic only need to replace the V layer, and the same layout with different logic only needs to replace the P or VM layer. However, in actual development, P or VM often degenerates into a one-to-one relationship because of the coupling of the display logic of the V layer (for example, SceneA needs to display "xxx+Name", and VM formats Name as "xxx + Name". One day, SceneB also uses this module, and all click events and page displays are the same, except that Name is displayed as "yyy + Name". At this time, VM is awkward because it is coupled with the display logic of SceneA). For such situations, there are usually two ways, one is to add states to the VM layer to determine the output state, and the other is to add another layer of FormatHelper outside the VM layer. The former may make the code ugly because of too many states, and the latter is more elegant and highly extensible, but too many layers are a bit clumsy when restoring data. You should choose according to your needs.

Here is a random comment. Some articles say that MVVM is to solve the problem of bloated C-layer and difficult testing of MVC. In fact, it is not true. According to the order of architecture evolution, the bloated C-layer is mostly due to the failure to split the MVC module properly. It is enough to split it properly, and there is no need for MVVM. The difficulty of testing MVC can also be solved by MVP, but MVP is not the best. The data interaction between VPs is too cumbersome, so MVVM was introduced. When the complete MVVM appeared, we looked at the origin from the result and found that it did a lot of things. In fact, it is not, and its predecessors have made a lot of efforts!

  • There are so many architectures, how should we choose in daily development?

Whether it is MVC, MVP, MVVM or MVXXX, the ultimate goal is to serve people. We focus on architecture and layering for development efficiency, and ultimately for fun. Therefore, in actual development, we should not stick to a certain architecture. Based on the actual project, ordinary MVC can cope with most development needs. As for MVP and MVVM, you can try them, but don't force them.

In short, I hope everyone can do this: when designing, have a clear idea in mind. When coding, just have fun.

<<:  A widget dancing on a needle with shackles on its legs

>>:  The fourth episode of the Aiti tribe consultation: Application scenarios and functions of Java message queues

Recommend

LG smartwatch real machine picture

Earlier, LG Electronics released a photo of its ne...

Why did sugarcane "conquer" the world?

Sugarcane attraction conquers the world Apart fro...

How can we reduce the uninstall rate of APP users?

There is a question that has been bothering App d...

WeChat group traffic diversion and operation methods!

Nowadays, many people believe that WeChat groups ...

How did people open cans when there were no cans?

Why not just change the name of the can to the di...

Low budget user growth model!

"Growth hacking" must be familiar to th...

It's called the "Queen of Flowers", but it has no petals...

In addition to the common wintersweets, plum blos...