[关闭]
@qidiandasheng 2020-12-29T12:54:29.000000Z 字数 6313 阅读 786

MVC,MVVM和MVP的区别(😁)

架构


MVC

基本MVC模式

如下图所示为基本的MVC(Model-View-Controller)模式结构图,可分为三部分:模型(Model)、视图(View)、控制器(Controller)。其在MVC模式中所扮演的角色分别为:

basic-MVC.png-87.3kB

其中,View和Controller依赖于Model,而Model并不依赖于View和Controller。这种设计模式的优点在于允许Model不受View的影响,从而能够进行独立的构建和测试。

此外,根据Model的具体实现还可以进一步分为:主动型Model、被动型Model。

被动型Model MVC模式

当只有一个Controller操控着Model时可以采用被动型Model。Controller定义Model,并在Model发生改变时通知View,后者再进行更新。在这种场景下,Model完全独立于View和Controller。实际上,被动型Model MVC模式就是基本的MVC模式。

主动型Model MVC模式

当Model的状态未受Controller干扰的情况下发生变化时,使用主动型Model。当其他来源正在更改数据并且必须立刻反应到View中时,可能会发生这种情况。

为了实现主动型Model,通常使用Observer模式来提供了一种机制来提醒其他对象的状态变化,避免引入依赖关系。各个View实现Observer接口并向Model注册。当Model发生变化时,Model会遍历所有注册的观察者并通知他们相关的变化。这种方法通常被称为“发布 - 订阅”。Model从不需要关于任何View的任何信息。事实上,在Controller需要被告知Model变化的情况下(例如,启用或禁用菜单选项),所有Controller必须通过实现Observer接口并订阅Model的变化。

active-model-MVC.png-135.8kB

传统版MVC

上述主动型Model MVC模式通过加入Observer模式进行了改良。事实上,随着业务需求的变化,MVC模式通过不断加入一些更基本的设计模式才演化成现在经典的MVC模式。这些基本模式协同工作,定义了MVC应用程序特有的功能分离和通信路径。

traditional-MVC.png-135.8kB

上图所示为传统MVC设计模式,其通过Composition、Strategy、Observer等基本设计模式协同工作以实现。用户操作在复合结构的某个层次上操作View,生成一个事件。Controller接收事件,并进行解释。这个过程使用Strategy模式实现,可以是通过消息请求一个Model对象来更新其状态或请求一个View对象来更新其行为或外观。Model对象则在其状态改变时通知所有已注册为观察者的对象。如果观察者是对象,则可以相应更新其外观。

苹果版MVC

苹果认为传统的MVC模式中,View通过Observer模式直接观察Model对象以获取相关的通知,而这样的设计会导致View和Model对象不能被广泛复用,因为View与其观察的Model之间存在耦合关系。因此,苹果版MVC与传统MVC基本一致,只是隔离了View和Model。

apple-MVC.png-116.5kB

导出图片Wed Apr 15 2020 19_22_32 GMT+0800 (中国标准时间).png-585.7kB

在iOS中,UIViewController和UIView是一一对应的。随着业务的深入,MVC最终一点点变成了Massive-View-Controller。

ios-MVC.png-103.6kB

优点

1、可以为一个模型在运行时同时建立和使用多个视图。变化-传播机制可以确保所有相关的视图及时得到模型数据变化,从而使所有关联的视图和控制器做到行为同步。

2、视图与控制器的可接插性,允许更换视图和控制器对象,而且可以根据需求动态的打开或关闭、甚至在运行期间进行对象替换。

3、模型的可移植性。因为模型是独立于视图的,所以可以把一个模型独立地移植到新的平台工作。需要做的只是在新平台上对视图和控制器进行新的修改。

4、潜在的框架结构。可以基于此模型建立应用程序框架,不仅仅是用在设计界面的设计中。

缺点

1、增加了系统结构和实现的复杂性。对于简单的界面,严格遵循MVC,使模型、视图与控制器分离,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率。

2、视图与控制器间的过于紧密的连接。视图与控制器是相互分离,但确实联系紧密的部件,视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用。

3、视图对模型数据的低效率访问。依据模型操作接口的不同,视图可能需要多次调用才能获得足够的显示数据。对未变化数据的不必要的频繁访问,也将损害操作性能。

MVP

MVP(Modell-View-Presenter)模式就是为了解决MVC中Controller越来越臃肿的问题,进一步明确代码分工。MVP与苹果版MVC非常相似,但是它们的从属关系有所不同(实线表示持有)。如下图所示,MVP模式中View持有Presenter,Presenter持有Model,View不能直接访问Model;而MVC模式中Controller持有View和Model。

通过修改从属关系,可以真正意义上实现将UI逻辑和数据逻辑隔离,而隔离之后就可以方便地对数据逻辑部分进行单元测试。

MVP.png-112.6kB

在iOS中,MVP的实现一般如下图所示。

ios-MVP.png-134.3kB

MVVM

MVVM(Model View View-Model)就是为了解决MVP中Presenter过于臃肿的问题。MVVM的思想是将Controller中UI控制逻辑与业务逻辑进行分离,并抽离出一个View-Model来完成UI控制的逻辑。而Controller只需要负责业务逻辑即可。如下图便是MVVM的结构图。

通常,View-Model可以调用Model定义的方法,从Model中获取数据以用于View,并对数据进行预处理,使View可以直接使用。View又可以向View-Model发出用户的操作命令,从而更改Model。MVVM实现了一种双向绑定机制。

在iOS中,MVVM的实现一般如下图所示。

ios-MVVM.png-99.4kB

MVVM的优点在于:降低了View和Model之间的耦合;分离了业务逻辑和视图逻辑。缺点在于:View和Model双向绑定导致bug难以定位,两者中的任何一方出现问题,另一方也会出现问题;增加了胶水代码。

MVVM和MVP的关系

MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。 唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。这样开发者就不用处理接收事件和View更新的工作,框架已经帮你做好了。

代码示例

Model

  1. @interface Person : NSObject
  2. - (instancetype)initwithSalutation:(NSString *)salutation firstName:(NSString *)firstName lastName:(NSString *)lastName birthdate:(NSDate *)birthdate;
  3. @property (nonatomic, readonly) NSString *salutation;
  4. @property (nonatomic, readonly) NSString *firstName;
  5. @property (nonatomic, readonly) NSString *lastName;
  6. @property (nonatomic, readonly) NSDate *birthdate;
  7. @end

MVC代码

  1. @implementation HomeController
  2. - (void)viewDidLoad {
  3. [super viewDidLoad];
  4. if (self.model.salutation.length > 0) {
  5. self.nameLabel.text = [NSString stringWithFormat:@"%@ %@ %@", self.model.salutation, self.model.firstName, self.model.lastName];
  6. } else {
  7. self.nameLabel.text = [NSString stringWithFormat:@"%@ %@", self.model.firstName, self.model.lastName];
  8. }
  9. NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
  10. [dateFormatter setDateFormat:@"EEEE MMMM d, yyyy"];
  11. self.birthdateLabel.text = [dateFormatter stringFromDate:model.birthdate];
  12. }
  13. @end

MVP代码

  1. ======================HomePresenter.h====================
  2. @protocol HomePresenterDelegate <NSObject>
  3. - (void)personDataName:(NSString *)nameText birthdate:(NSString *)birthdateText;
  4. @end
  5. @interface HomePresenter : NSObject
  6. @property(nonatomic, weak)id<HomePresenterDelegate> delegate;
  7. - (void)getShowDataWithPerson:(Person *)person;
  8. @end
  9. ======================HomePresenter.m====================
  10. @implementation HomePresenter
  11. - (void)getShowDataWithPerson:(Person *)person{
  12. NSString *nameText;
  13. if (person.salutation.length > 0) {
  14. nameText = [NSString stringWithFormat:@"%@ %@ %@", self.person.salutation, self.person.firstName, self.person.lastName];
  15. } else {
  16. nameText = [NSString stringWithFormat:@"%@ %@", self.person.firstName, self.person.lastName];
  17. }
  18. NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
  19. [dateFormatter setDateFormat:@"EEEE MMMM d, yyyy"];
  20. NSString *birthdateText = [dateFormatter stringFromDate:person.birthdate];
  21. [self.delegate personDataName:nameText birthdate:birthdateText]
  22. }
  23. @end
  1. @interface HomeController ()<HomePresenterDelegate>
  2. @property (nonatomic, strong)HomePresenter *homePresenter;
  3. @property (nonatomic, strong)Person *person;
  4. @end
  5. @implementation HomeController<>
  6. - (void)viewDidLoad {
  7. [super viewDidLoad];
  8. [self.homePresenter getShowDataWithPerson:self.person];
  9. }
  10. - (void)personDataName:(NSString *)nameText birthdate:(NSString *)birthdateText{
  11. self.nameLabel.text = nameText;
  12. self.birthdateLabel.text = birthdateText;
  13. }
  14. @end

MVVM代码

  1. @interface PersonViewModel : NSObject
  2. @property (nonatomic, strong) Person *person;
  3. @property (nonatomic, copy) NSString *nameText;
  4. @property (nonatomic, copy) NSString *birthdateText;
  5. @end
  6. @implementation PersonViewModel
  7. - (void)setPerson:(Person * _Nonnull)person{
  8. _person = person;
  9. if (_person.salutation.length > 0) {
  10. self.nameText = [NSString stringWithFormat:@"%@ %@ %@", self.person.salutation, self.person.firstName, self.person.lastName];
  11. } else {
  12. self.nameText = [NSString stringWithFormat:@"%@ %@", self.person.firstName, self.person.lastName];
  13. }
  14. NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
  15. [dateFormatter setDateFormat:@"EEEE MMMM d, yyyy"];
  16. self.birthdateText = [dateFormatter stringFromDate:self.person.birthdate];
  17. }
  18. @end
  1. @interface HomeController ()<HomePresenterDelegate>
  2. @property (nonatomic, strong)PersonViewModel *personViewModel;
  3. @property (nonatomic, strong)Person *person;
  4. @end
  5. @implementation HomeController
  6. - (void)viewDidLoad {
  7. [super viewDidLoad];
  8. RACChannelTo(self.nameLabel, text) = RACChannelTo(self.personViewModel, nameText);
  9. RACChannelTo(self.birthdateLabel, text) = RACChannelTo(self.personViewModel, birthdateText);
  10. }
  11. - (void)buttonGetShowData{
  12. [self.personViewModel setPerson:self.person];
  13. }
  14. @end

我们能看到相对于MVP,MVVM只要改变View Model的属性,那View的属性也会同时更新,主要就是通过绑定机制来处理一个双向绑定,我这里使用的是ReactiveCocoa。而MVP则是由其中的P主动对View进行一个属性的更新。

参考

MVVM 介绍
MVC、MVP、MVVM的演化

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注