Model View Controller、Model View Presenter 和 Model View ViewModel 设计模式






4.79/5 (58投票s)
本文将对 MVC、MVP 和 MVVM 进行比较和对比,并根据您选择的技术和要解决的问题,提出适合使用的模式。
引言
随着以 UI 为中心的技术的快速发展,人们对表示层设计模式的兴趣重新燃起。MVC(Model-View-Controller)是最常被引用的模式之一,据信是由挪威计算机工程师 Trygve Reenskaug 在 1979 年开发 Smalltalk-80 时设计的[1]。随后,在 1994 年极具影响力的《设计模式:可复用面向对象软件的基础》[2](又称“四人帮”书籍)中对其进行了深入描述。两年后,Taligent(IBM)的 Mike Potel 发表了他的论文《Model-View-Presenter (MVP) - Taligent C++ 和 Java 的编程模型》[3],旨在解决 MVC 的不足。MVP 和 MVC 此后被 Microsoft 广泛采用于其 Composite Application Blocks (CAB) 和 ASP.NET MVC 框架中。
这些模式及其关联在 2004 年 Martin Fowler 在他的论文中对它们进行分析时再次受到关注。他建议将它们分解成更小的模式(Supervising Controller 和 Passive View),并在他的论文《Presentation Model (PM)》[6] 中提出了解决方案。随着 Windows Presentation Foundation (WPF) 的发布,新的术语 Model-View-ViewModel 进入了人们的视野。2005 年,WPF 架构师 John Gossman 在他的博客上首次提到它[7],随后 Josh Smith 在 MSDN 文章《WPF Apps with the Model-View-ViewModel Design Pattern》[8] 中对其进行了描述。MVVM 是围绕 WPF 架构构建的,它封装了 MVC、MVP 和 PM 设计模式的元素。
本文将对 MVC、MVP 和 MVVM 进行比较和对比,并根据您选择的技术和要解决的问题,提出适合使用的模式。
MVC
细读《设计模式》一书的读者会注意到,MVC 不被称为设计模式,而是“用于构建用户界面的类集合”,它使用了 Observer、Strategy 和 Composite 等设计模式。它还使用了 Factory Method 和 Decorator,但主要的 MVC 关系由 Observer 和 Strategy 模式定义。
有三种类型的对象。Model 是我们的应用程序数据,View 是屏幕,Controller 定义了 View 如何响应用户输入。View 和 Model 使用发布-订阅协议——当 Model 数据发生更改时,它会更新 View。它允许我们将多个 View 附加到同一个 Model[2]。这是通过使用 Observer 设计模式实现的。
此模式的目标是定义 Subject 和 Observers 之间的一对多关系;如果 Subject 发生更改,所有 Observer 都会更新。Subject 维护 Observer 列表,并可以向列表中添加或移除对象。Observer 反过来在其接口上公开一个 `Update` 方法,Subject 可以使用该方法更新它观察到的所有对象。C# 实现将如下所示
public abstract class Subject
{
private readonly ICollection<Observer> Observers =
new Collection<Observer>();
public void Attach(Observer observer)
{
Observers.Add(observer);
}
public void Detach(Observer observer)
{
Observers.Remove(observer);
}
public void Notify()
{
foreach (Observer o in Observers)
{
o.Update();
}
}
}
public class ConcreteSubject : Subject
{
public object SubjectState { get; set; }
}
public abstract class Observer
{
public abstract void Update();
}
public class ConcreteObserver : Observer
{
private object ObserverState;
private ConcreteSubject Subject { get; set; }
public ConcreteObserver(ConcreteSubject subject)
{
Subject = subject;
}
public override void Update()
{
ObserverState = Subject.SubjectState;
}
}
MVC 模式的另一个组成部分是 View-Controller 关系。View 使用 Controller 来实现特定类型的响应。可以更改 Controller,使 View 对用户输入做出不同的响应。这种 View-Controller 链接是 Strategy 设计模式的一个示例。
每个 `ConcreteStrategy` 都封装了一种特定的响应类型。`Context` 对象具有对 `Strategy` 对象的引用,并通过通用接口将请求转发给特定的 `Strategy`。这里是一个 C# 代码
public abstract class Strategy
{
public abstract void AlgorithmInterface();
}
public class ConcreteStrategyA : Strategy
{
public override void AlgorithmInterface()
{
// code here
}
}
public class Context
{
private readonly Strategy Strategy;
public Context(Strategy strategy)
{
Strategy = strategy;
}
public void ContextInterface()
{
Strategy.AlgorithmInterface();
}
}
现在我们知道 Model 作为 Observer 模式的 Subject,View 充当 Observer 对象。在 MVC 的另一种关系中,View 是 Context,Controller 是 Strategy 对象。结合我们对两个图的了解,我们可以绘制如下的 MVC UML 类图
这里是 MVC 模式的实现代码
public abstract class Model
{
private readonly ICollection<View> Views = new Collection<View>();
public void Attach(View view)
{
Views.Add(view);
}
public void Detach(View view)
{
Views.Remove(view);
}
public void Notify()
{
foreach (View o in Views)
{
o.Update();
}
}
}
public class ConcreteModel : Model
{
public object ModelState { get; set; }
}
public abstract class View
{
public abstract void Update();
private readonly Controller Controller;
protected View()
{
}
protected View(Controller controller)
{
Controller = controller;
}
public void ContextInterface()
{
Controller.AlgorithmInterface();
}
}
public class ConcreteView : View
{
private object ViewState;
private ConcreteModel Model { get; set; }
public ConcreteView(ConcreteModel model)
{
Model = model;
}
public override void Update()
{
ViewState = Model.ModelState;
}
}
public abstract class Controller
{
public abstract void AlgorithmInterface();
}
public class ConcreteController : Controller
{
public override void AlgorithmInterface()
{
// code here
}
}
如果为了简化而省略具体类,我们将得到一个更熟悉的 MVC 图。请注意,我们使用伪 UML 形状而非标准 UML 形状,其中圆圈代表类组(例如,Model 和 ConcreteModel),而不是 Figure 3 的 UML 类图上的类。
MVP
MVP 最初由 Taligent(IBM)的 Mike Potel 于 1996 年描述。Potel 在其 MVP 的工作中[3]质疑了 MVC 中 Controller 类的必要性。他注意到现代操作系统用户界面在 View 类中已经提供了大部分 Controller 功能,因此 Controller 显得有些冗余。
Potel 在他的论文中分析了 View 与 Model 交互的类型。他将用户操作分为选择、命令执行和事件触发。因此,他定义了 Selection 和 Command 类,顾名思义,它们可以选定 Model 的一个子集并执行操作,还引入了 Interactor 类来封装改变数据的事件。新类称为 Presenter,它封装了 Selection、Command 和 Interactor。
随着 MVP 的发展,Dolphin MVP 框架的开发团队在 Andy Bower 和 Blair McGlashan 的论文[4]中概述了他们的版本。他们的版本与 Potel 的 MVP 类似,但还审查了 Model-View 关系。
正如我们所见,Model-View 关系是基于 Observer 设计模式的间接关系。Model 可以通知 View 新数据已到达,View 可以从其订阅的 Model 中更新其数据。Bower 和 McGlashan 质疑了这种间接链接的性质,并建议 Model 可以直接访问用户界面。
基本思想是“扭曲 MVC”,其中 View 吸收了 Controller 的功能,并添加了新类(Presenter)。Presenter 可以直接访问 View 和 Model,并且在相关时,Model-View 关系仍然存在。总的来说,View 显示数据,Presenter 可以直接更新 Model 和 View。
如果我们添加 Interactor、Selection 和 Command 类,我们将得到完整的图。MVP 有不同的风格,正如我们所见,Presenter 可以直接访问 View,或者基于 Observer 模式的 Model-View 关系仍然存在。在此版本中,我们省略了我们在 MVC 代码示例中看到的 Model-View 链接,并假设 Presenter 可以直接更新 View。
这是实现代码
public class Model
{
public object ModelState { get; set; }
}
public class View
{
private object ViewState;
// user clicks a button
public static void Main()
{
Interactor interactor = new Interactor();
Presenter presenter = new Presenter(interactor);
interactor.AddItem("Message from the UI");
}
public void Update(object state)
{
ViewState = state;
}
}
public class Presenter
{
private readonly Command Command;
private readonly Interactor Interactor;
private readonly Model Model;
private readonly View View;
public Presenter(Interactor interactor)
{
Command = new Command();
Model = new Model();
View = new View();
Interactor = interactor;
Interactor.ItemAdded +=
new Interactor.AddItemEventHandler(InteractorItemAdded);
}
private void InteractorItemAdded(object sender, Selection e)
{
// call command
Command.DoCommand();
// update Model
Model.ModelState = e.State;
// some processing of the message here
// ...
// update View
View.Update("Processed " + e.State);
}
}
public class Selection : EventArgs
{
public object State { get; set; }
}
public class Command
{
public void DoCommand()
{
// code here
}
}
public class Interactor
{
public delegate void AddItemEventHandler(object sender, Selection e);
public event AddItemEventHandler ItemAdded;
protected virtual void OnItemAdded(Selection e)
{
if (ItemAdded != null)
ItemAdded(this, e);
}
public void AddItem(object value)
{
Selection selection = new Selection {State = value};
OnItemAdded(selection);
}
}
Presentation Model
Martin Fowler 在其关于 GUI 架构的工作[5]中,不仅深入分析了 UI 设计模式,还重新命名了一些旧模式,赋予它们在现代开发框架中的新生命。
MVC 最基本的思想是将表示对象和域模型对象分开。Fowler 将其称为 Separated Presentation 模式。Separated Presentation 赋予我们 Model 和 View。他们用于通信的特定 Observer 模式风格,他称之为 Observer Synchronization,这与 Flow Synchronization[5]不同。因此,根据 Fowler 的说法,Model 和 View 以及它们之间的间接链接是定义 MVC 的核心元素。他称之为——Separated Presentation 和 Observer Synchronization 的元素。
他还指出两种截然不同的 MVP 描述——一种由 Potel 提出,另一种由 Bower 和 McGlashan 提出。Potel 只关注移除 Controller 并将更多工作委托给 View。Fowler 将这种方法称为 Supervising Controller。Bower 和 McGlashan 描述的另一项功能,即 Presenter 直接更新 View 的能力,他称之为 Passive View。
通过将这些模式分解成更小的部分,Fowler 为开发人员提供了他们可以用来实现特定行为的工具,而不是 MVC 或 MVP 模式本身。开发人员可以从 Separated Presentation、Observer Synchronization、Supervising Controller 或 Passive View 中进行选择。
尽管 MVC 和 MVP 都很强大,但它们也存在问题。其中一个问题是 View 状态的持久性。例如,如果 Model 是一个域对象,它对 UI 一无所知,而 View 也不实现任何业务逻辑,那么我们将把 View 元素(如选定项)的状态存储在哪里?Fowler 提出了 Presentation Model 模式的解决方案。他承认该模式的根源在于 Application Model——Visual Works Smalltalk 团队的劳动成果。Application Model 在 Model 和 View 之间引入了另一个可以存储状态的类。对于 View 而言,ApplicationModel 类成为其 Model,而域特定的 Model 与 ApplicationModel 进行交互,后者成为其 View。ApplicationModel 知道如何更新 UI,但本身不直接引用任何 UI 元素。
就像 Smalltalk 的 Application Model 一样,Presentation Model 类位于 Model 和 View 之间。它通过使用发布-订阅或数据绑定机制丰富 Model 的数据,包括状态,这些状态会与 View 同步。View 触发一个事件来更新 Presentation Model 中的状态,并相应地更新其状态。
此模型允许您在 View 或 Presentation Model 中实现同步代码。因此,View 是否应该引用 Presentation Model,或者 Presentation Model 是否应该引用 View,都取决于开发人员的决定[6]。
我们可以绘制如下图
这是代码
public abstract class PresentationModel
{
private readonly ICollection<View> Views = new Collection<View>();
public void Attach(View view)
{
Views.Add(view);
}
public void Detach(View view)
{
Views.Remove(view);
}
public void Notify()
{
foreach (View o in Views)
{
o.Update();
}
}
}
public class ConcretePresentationModel : PresentationModel
{
public Model Model = new Model();
public object ModelState { get; set; }
}
public class Model
{
public void GetData()
{
}
}
public abstract class View
{
public abstract void Update();
}
public class ConcreteView : View
{
private object ViewState;
private ConcretePresentationModel Model { get; set; }
public ConcreteView(ConcretePresentationModel model)
{
Model = model;
}
public override void Update()
{
ViewState = Model.ModelState;
}
}
MVVM
MVVM 一词最早由 WPF 架构师 John Gossman 于 2005 年在博客上提到[7]。随后 Josh Smith 在他的 MSDN 文章“WPF Apps with the Model-View-ViewModel Design Pattern”[8]中对其进行了深入描述。
Gossman 解释说,MVVM 的理念是围绕现代 UI 架构构建的,其中 View 由设计师负责,而不是开发人员,因此不包含任何代码。就像它的 MVC 前辈一样,MVVM 中的 View 可以绑定到数据并显示更新,但完全无需编码,只需使用 XAML 标记扩展。这样,View 就处于设计师的控制之下,但可以通过 WPF 绑定机制从域类更新其状态。这符合 Presentation Model 模式的描述。
这就是为什么 MVVM 与 PM 相似,其中 View 将引用 Presentation Model,称为 ViewModel,这是一个更好的名称,因为它是一个“View 的模型”。与 Presentation Model 不同,MVVM 中的 ViewModel 也像 MVP 中的 Presenter 一样封装了命令。
总的来说,View 构建 UI 并绑定到 ViewModel。请参阅以下代码
< Window x:Class="WpfApplication1.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="View" Height="300" Width="300">
< UniformGrid Columns="2">
< TextBlock Text="{Binding State}" />
< Button Command="{Binding GetStateCommand}">Update< /Button>
< /UniformGrid>
< /Window>
C# 代码
public partial class View : Window
{
public View()
{
ViewModel viewModel = new ViewModel();
DataContext = viewModel;
InitializeComponent();
}
}
ViewModel 提供状态等数据,并包含命令。它还与提供域特定对象的 Model 进行交互。
public class ViewModel : INotifyPropertyChanged
{
private ICommand command;
private string state;
private Model model = new Model();
public string State
{
get { return state; }
set
{
state = value;
OnPropertyChanged("State");
}
}
public ICommand GetStateCommand
{
get
{
if (command == null)
{
command = new RelayCommand(param => DoCommand(),
param => CanDoCommand);
}
return command;
}
private set { command = value; }
}
private void DoCommand()
{
State = model.GetData();
}
private bool CanDoCommand
{
get { return model != null;}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var propertyChangedEventArgs =
new PropertyChangedEventArgs(propertyName);
if (PropertyChanged != null)
{
PropertyChanged(this, propertyChangedEventArgs);
}
}
#endregion
}
/// <summary>
/// http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
/// A command whose sole purpose is to
/// relay its functionality to other
/// objects by invoking delegates.
/// </summary>
public sealed class RelayCommand : ICommand
{
#region Fields
readonly Action<object> m_execute;
readonly Predicate<object> m_canExecute;
#endregion // Fields
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> execute)
: this(execute, null)
{ }
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
m_execute = execute;
m_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return m_canExecute == null ? true : m_canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
m_execute(parameter);
}
#endregion // ICommand Members
}
public class Model
{
public string GetData()
{
return "DataFromtheModel";
}
}
此模式被 WPF 开发人员广泛采用,并且可以在下面的 UML 图中看到
摘要
正如我们所见,MVC 的根本思想是将域逻辑和 GUI 对象分离到 Model 和 View 中。这两者通过使用称为 Observer 模式的发布-订阅机制间接连接。此设计的另一个元素是 Controller,它为 View 实现特定的策略。
当 View 非常简单且不包含任何代码时(例如 Web 系统和 HTML),使用此模式是合理的。这样,View 可以构建 UI,Controller 处理用户交互。请注意,Microsoft 在其针对 Web 开发人员的 ASP.NET MVC 框架中选择了使用此模式。
MVP 将更多工作委托给 View 并移除了 Controller。它引入了 Presenter 类,该类封装了 View 的状态和命令。Presenter 可以直接访问 View。这对于 View 是强大类且可以封装事件和存储项目状态的 Windows 系统来说非常理想。请注意,Microsoft 已将其纳入 Composite Application Blocks (CAB) 框架,该框架面向 Windows 开发人员。
基于 Observer 模式的 Model-View 关系在 MVP 中仍然存在;然而,Presenter 可以直接访问 View。虽然这易于使用和实现,但开发人员需要小心,不要破坏他们试图建模的系统的逻辑。例如,驱动程序加速并检查速度指示器的系统不太可能使用 MVP 进行建模。Driver(Presenter)可以更新引擎状态(Model),但现在需要更新速度指示器(View)。当然,更符合逻辑的是驱动程序更新引擎状态(踩油门)并且速度指示器读取这些变化(Observer 模式)的系统。
MVVM 是所有 WPF 开发人员的强大模式。它允许 View 保持不包含任何代码,并可供 XAML 设计师使用。WPF 绑定作为 ViewModel 的链接,ViewModel 可以处理状态、实现命令并与域特定 Model 进行通信。
MVVM 类似于 Fowler 的 PM,其中 Presentation Model 类称为 ViewModel。
Presentation Model 是 MVVM 的更通用版本,开发人员需要自己实现 View 和 ApplicationModel(ViewModel)之间的链接。它还允许 View 引用 ApplicationModel,或者 ApplicationModel 引用 View。它可以分解成更小的模式,如 Supervising Controller 或 Passive View,其中只需要特定的行为。
总的来说,我希望本文能够实现其目标,揭示主要的 GUI 模式并强调它们的优缺点。它可以作为一个很好的起点,并帮助您在开始下一次 GUI 开发之旅时决定使用哪种模式。
参考文献
- MVC:XEROX PARC 1978-79 (1979) 作者:Trygve Reenskaug,http://heim.ifi.uio.no/~trygver/themes/mvc/mvc-index.html
- 设计模式:可复用面向对象软件的基础 (1994) 作者:Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, ISBN 0-201-63361-2
- MVP:Model-View-Presenter:Taligent C++ 和 Java 的编程模型 (1996) 作者:Mike Potel,http://www.wildcrest.com/Potel/Portfolio/mvp.pdf
- Twisting the Triad 作者:Andy Bower, Blair McGlashan,http://www.object-arts.com/papers/TwistingTheTriad.PDF
- GUI 架构 作者:Martin Fowler,https://martinfowler.com.cn/eaaDev/uiArchs.html
- Presentation Model (2004) 作者:Martin Fowler,https://martinfowler.com.cn/eaaDev/PresentationModel.html
- 用于构建 WPF 应用程序的模型/视图/视图模型模式简介 (2005) 作者:John Gossman,http://blogs.msdn.com/johngossman/archive/2005/10/08/478683.aspx
- 使用模型-视图-视图模型设计模式的 WPF 应用程序 (2009) 作者:Josh Smith,http://msdn.microsoft.com/en-us/magazine/dd419663.aspx