微信号:programmer_club

介绍:程序员第一自媒体,与你探讨码农人生路上遇到的各类泛技术话题,定期为你推荐码农人生思考、感悟以及启迪!

MVC, MVP, MVVM比较以及区别

2017-04-29 22:59 JustRun

作者:JustRun

原文:cnblogs.com/JustRun1983/p/3679827.html

点击文末阅读原文即可前往) 


MVC, MVP和MVVM都是用来解决界面呈现和逻辑代码分离而出现的模式。以前只是对它们有部分的了解,没有深入的研究过,对于一些里面的概念和区别也是一知半解。现在一边查资料,并结合自己的理解,来谈一下对于这三种模式思想的理解,以及它们的区别。


一,MVC, MVP, MVVM诞生的需求?


软件中最核心的,最基本的东西是什么? 是的,是数据。我们写的所有代码,都是围绕数据的。


围绕着数据的产生、修改等变化,出现了业务逻辑。


围绕着数据的显示,出现了不同的界面技术。 


没有很好设计的代码,常常就会出现数据层(持久层)和业务逻辑层还有界面代码耦合的情况。


ORM等框架,解耦合了业务逻辑和数据之间的耦合,业务逻辑不再关心底层数据如何存储和读取。所有数据呈现给业务逻辑层的就是一个个的对象。


而MVC, MVP, MMVM用来解决业务逻辑和视图之间的耦合。


二,一段典型的耦合代码


{

SqlDataAdapter adapter = new SqlDataAdapter("select * from Table1","server=.;database=db;uid=sa;pwd=password");

DataSet ds = new DataSet("ds1");

adapter.Fill(ds);

this.GridView1.DataSource = ds;

this.GridView1.DataBind();

}


上面的这段代码中,既包含了数据访问,还包含的页面展示。当项目复杂程度更高,这种代码就会变得非常难以维护,层次也不清晰。


三,MVC模式


MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写。MVC开始是存在于桌面程序中的,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式


3.1 主动MVC


MVC的理论思想对应的是主动MVC, 这里的主动的意思是, Model会主动通知View更新。而我们使用MVC框架, Struts, asp.net mvc等都不是主动MVC(视图的更新都是通过Controller完成的)


Model


用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。


模型中数据的变化一般会通过一种刷新机制被公布。为了实现这种机制,那些用于监视此模型的视图必须事先在此模型上注册,从而,视图可以了解在数据模型上发生的改变。


View


视图层负责数据的展示。


在视图中一般没有程序上的逻辑。为了实现视图上的刷新功能,视图需要访问它监视的数据模型(Model),因此应该事先在被它监视的数据那里订阅Model的事件。


Controller


控制器是M和V之间的连接器,用于控制应用程序的流程。它处理事件并作出响应。“事件”包括用户的行为和数据模型上的改变。



3.2 被动MVC


下图是被动MVC中的流程,和主动MVC不同之处是, View没有订阅Model数据变化的事件,等待Model来通知需要根据新的数据来更新View. 在被动MVC中,Controller负责通知View, 有数据变化,需要更新视图。



被动MVC 中,与主动MVC的区别在于: 


1、模型对视图和控制器一无所知,它仅仅是被它们使用 


2、控制器使用视图,并通知它更新数据显示 


3、视图仅仅是在控制器通知它去模型取数据的时候它才这么做(视图并不会订阅或监视模型的更新) 


3.3. Web应用中的MVC框架


Web中的MVC框架都是被动MVC模式,因为web应用中, 由于http是基于请求和响应方式协同工作的,因此当服务器端的model(数据)发生变化时,它不会立即更新客户端的view,只有客户端重新请求或刷新页面时才更新.


下图是典型的MVC框架中的MVC一个请求流程。



3.4 MVC总结


MVC优点


  • 由于MVC很好的分离了视图层和业务层,所以它具有以下优点

  • 耦合性低

  • 开发速度快

  • 可维护性高

  • 没有控件的概念,对html没有封装,易于理解

  • 和其它平台(java, php)等更加相似。便于人才获取


MVC使用的误区


1.把Model理解成实体类(Entity),在MVC中Model应该包含2部分功能,一部分是处理业务逻辑,一部分是提供View显示的数据


2.把业务逻辑全部放在Controller端这两个误区本质上都是对Model的作用不明导致的。Model在MVC架构中起的作用非常重要,它应该是业务逻辑真正的实现层。所以Model的实际上是Business Model(业务模型)。而Controller仅仅起一个“桥梁”作用,它负责把View的请求转发给Model,再负责把Model处理结束的消息通知View。Controller是用来解耦View和Model的,具体一点说,就是为了让UI与逻辑分离(界面与代码分离)。


引自http://www.techopedia.com/definition/27454/model-mvc-aspnet


Techopedia explains Model (MVC)


The Model is the part of MVC which implements the domain logic. In simple terms, this logic is used to handle the data passed between the database and the user interface (UI).


The Model is known as domain object or domain entity. 

The domain objects are stored under the Models folder in ASP.NET. The domain model represents the application perspective for the data to be handled whereas a view model is required to produce the engine that generates the View.


This definition was written in the context of ASP.NET.


MVC的缺点


完美的MVC应用场景应该是这样的:


有个Student Model, 关联StudentListView,  StudentEditView.


对于StudentListView, Student Model提供Student的集合数据来显示StudentListView


对于StudentEditView, Student Model提供单个Student数据来展示StudentEditView并且响应StudentEditView的保存操作。


但是这只是完美的情况,实际应用中,在ListView上,不单单显示Student的信息,可能还需要这个Student的历史成绩,家庭情况,  老师信息。而这些是Student Model不能提供的。


也许我们可以扩展Student Model, 将Student Model能够提供的信息扩展,包含成绩信息等,这本身也可以。但是,如果Student显示的View,这个需要只是需要额外的成绩信息,另一个View只是需要额外的家庭信息,Student Model是不是有些疲于奔命,你能知道还会有多少个差异化的View的需求? 而且让逻辑端代码这样不断的修改来适应View端,好吗?


由于MVC的设计思想是从Model出发,而没有考虑到View端的复杂性,这样导致的问题是Model难以符合复杂多变的View端变化。


相对这点,MVP和MVVM就要好得多。它们都独立出了Presenter 和ViewModel来对应每个View。


四、MVP模式


MVP模式也是一种经典的界面模式。MVP中的M代表Model, V是View, P是Presenter。


下面例子中的完整代码:  WinformMVP源码(http://files.cnblogs.com/JustRun1983/WinFormMVP.zip)


大家还可以比较园中Artech的这篇文章 谈谈关于MVP模式中V-P交互问题(http://www.cnblogs.com/artech/archive/2010/03/25/1696205.html)


4.1 MVP的思想


MVP模式在我看来,是一个真正意义上的隔离View的细节和复杂性的模式。为什么这么说:


因为在其它模式中V都代表的是UI界面, 是一个html页面,XAML文件或者winform界面。但是在MVP模式中的V代表的是一个接口,一个将UI界面提炼而抽象出来的接口。接口意味着任何实现了该接口的界面,都能够复用已有的Presenter和Model代码。


4.2 UI界面接口化


要很好的理解MVP, 就要有把UI界面接口化的能力。看下面的界面中,将红色标记的User Control抽象一下,就能得到下面的接口



public interface IUserAdd 

       event EventHandler UserAddEvent;

       string UserName { get; set; }

       string UserAge { get; set; }

}


界面中的2个输入框被抽象成了UserName和UserAge两个属性。Save按钮的点击事件,被抽象成了事件UserAddEvent。winform中实现该接口的代码如下:


public partial class UserAdd : UserControl, IUserAdd 

       public event EventHandler UserAddEvent; 

       public string UserName 

       { 

           set { this.txbName.Text = value; } 

           get { return this.txbName.Text; } 

       }

       public string UserAge 

       { 

           set { this.txbAge.Text = value; } 

           get { return this.txbAge.Text; } 

       }

       public UserAdd() 

       { 

           InitializeComponent(); 

       }

       private void btnAdd_Click(object sender, EventArgs e) 

       { 

          if (UserAddEvent != null) UserAddEvent(this, e); 

       } 

   }


下面拿UserAge属性来解释一下,UI界面接口化的魔力。当后端代码要获取界面上的年龄值,就只需要get属性, 要更新界面显示的时候,就只需要set属性。

这个时候,后端代码对于界面的操作,被抽象成了对于UserAge属性的操作了,也就是和具体的界面显示无关了。


4.3 Presenter —— Model和View之间的桥梁


上文提到的后端代码中,包含了P和M. M和MVC中一样,指的是逻辑代码。P则是Model和View之间的桥梁,负责将对应的Model和View组合到一起。


针对上面的IUserAdd, 对应的Presenter代码是:


public class UserAddPresenter:IPresenter 

       private readonly IUser _model; 

       private readonly IUserAdd _view; 

       private readonly ApplicationFacade _facade = ApplicationFacade.Instance; //这里的facade是Presenter之间通信用的,详细可以看完整代码

      //Presenter构造函数中,将view和model作为参数传入

       public UserAddPresenter(IUser model, IUserAdd view) 

       { 

           _model = model; 

           _view = view; 

           WireUpViewEvents(); 

       }

       private void WireUpViewEvents() 

       { 

           _view.UserAddEvent += _view_UserAdd; 

       }

      //当view的UserAdd事件触发,取得UI中的数据,调用model逻辑处理,添加新用户。

     //同时发送User_ADDED消息到系统中(系统中其它UI部分接收消息,比如这里的DataGrid,它接收到User_ADDED之后,会刷新)

       private void _view_UserAdd(object sender, EventArgs e) 

       { 

           var user = new User 

                      { 

                          Name = _view.UserName, 

                          Age = Convert.ToInt32(_view.UserAge) 

                      };

           _model.AddItem(user); 

           _facade.SendNotification(ApplicationFacade.USER_ADDED); 

       }

}


4.4 MVP的代码结构和时序图


这里的MVP中的代码结构图和时序图,能够更好的帮助理解MVP模式




4.5 MVP模式总结


在MVP里,Presenter完全把Model和View进行了分离,主要的程序逻辑在Presenter里实现。


而且,Presenter与具体的 View是没有直接关联的,而是通过定义好的接口进行交互,从而使得在变更View时候可以保持Presenter的不变,即重用! 


不仅如此,我们还可以编写测试用的View,模拟用户的各种操作,从而实现对Presenter的测试 —— 而不需要使用自动化的测试工具。


 我们甚至可以在Model和View都没有完成时候,就可以通过编写Mock Object(即实现了Model和View的接口,但没有具体的内容的)来测试Presenter的逻辑。


MVP的优势


1、模型与视图完全分离,我们可以修改视图而不影响模型 


2、可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部 


3、我们可以将一个Presener用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁。 


4、如果我们把逻辑放在Presenter中,那么我们就可以脱离用户界面来测试这些逻辑(单元测试)


五, MVVM模式


5.1 MVVM模式的设计思想


MVVM模式中,一个ViewModel和一个View匹配,它没有MVP中的IView接口,而是完全的和View绑定,所有View中的修改变化,都会自动更新到ViewModel中,同时ViewModel的任何变化也会自动同步到View上显示。


这种自动同步之所以能够的原因是ViewModel中的属性都实现了observable这样的接口,也就是说当使用属性的set的方法,都会同时触发属性修改的事件,使绑定的UI自动刷新。


(在WPF中,这个observable接口是 INotifyPropertyChanged; 在knockoutjs中,是通过函数ko.observable() 和ko.observrableCollection()来实现的)


所以MVVM比MVP更升级一步,在MVP中,V是接口IView, 解决对于界面UI的耦合; 而MVVM干脆直接使用ViewModel和UI无缝结合, ViewModel直接就能代表UI. 但是MVVM做到这点是要依赖具体的平台和技术实现的,比如WPF和knockoutjs, 这也就是为什么ViewModel不需要实现接口的原因,因为对于具体平台和技术的依赖,本质上使用MVVM模式就是不能替换UI的使用平台的.


5.2 MVVM模式结构图


这里是MVVM模式的结构图,能够帮助更加容易的理解MVVM模式:



六, MVC, MVP和MVVM模式使用场景总结


由于在winform中无法像WPF一样,支持数据和界面的双向绑定以及事件的监控,所以,在winform中MVP是最佳选择。


WPF和html界面中使用Knockout,实现了observable, 所以使用MVVM.(应该说WPF就是为使用MVVM设计的)


在web应用中,由于http是基于请求和响应方式协同工作的, 无法一直保持连接状态,所以无法达到MVP中Presenter之间的消息传递和MVVM中的ViewModel和界面之间的绑定, 所以MVC是最佳的选择。




微信公众号内回复数字“1”

小编拉你进粉丝微信群

不是在文章评论里回复

 
程序员之家 更多文章 如果只有一个月的时间学 iOS 开发,我们该做些什么? 金庸笔下的良好代码风格 代码真的有必要写到完美吗? 为什么程序猿会选择跳槽? 女生的大白兔到底有多软?
猜您喜欢 十个值得一试的开源深度学习框架 SQL Hint都无法解救DB性能时,如何通过视图曲线求国? 爬虫VS反爬虫的蝴蝶效应【岂安低调分享】 新手学习设计的五大雷区&三个独门秘籍! 认识微服务