当超越RAD(拖放和配置)构建用户界面的方式时,许多工具鼓励您可能会遇到三种设计模式,称为Model-View-Controller、Model-View-Presenter和Model-View-ViewModel。我的问题分为三个部分:
- 这些模式解决了哪些问题?
- 它们有何相似之处?
- 它们有何不同?
当超越RAD(拖放和配置)构建用户界面的方式时,许多工具鼓励您可能会遇到三种设计模式,称为Model-View-Controller、Model-View-Presenter和Model-View-ViewModel。我的问题分为三个部分:
在MVP中,Presenter 包含 View 的 UI 业务逻辑。View 委托的所有调用都直接发送给 Presenter。Presenter 也直接与 View 解耦,并通过接口与其对话。这是为了允许在单元测试中模拟视图。MVP 的一个共同属性是必须有大量的双向调度。例如,当有人单击“保存”按钮时,事件处理程序将委托给 Presenter 的“OnSave”方法。保存完成后,Presenter 将通过其接口回调 View,以便 View 可以显示保存已完成。
MVP 往往是在 WebForms 中实现分离表示的一种非常自然的模式。原因是视图总是首先由 ASP.NET 运行时创建。您可以找到有关这两种变体的更多信息。
被动视图:视图尽可能愚蠢,包含几乎为零的逻辑。Presenter 是与 View 和 Model 对话的中间人。View 和 Model 完全相互屏蔽。Model 可能会引发事件,但 Presenter 订阅它们以更新 View。在 Passive View 中没有直接的数据绑定,相反,View 公开了 Presenter 用来设置数据的 setter 属性。所有状态都在 Presenter 而不是 View 中管理。
监督控制器:演示者处理用户手势。View 通过数据绑定直接绑定到 Model。在这种情况下,Presenter 的工作是将 Model 传递给 View,以便它可以绑定到它。Presenter 还将包含诸如按下按钮、导航等手势的逻辑。
在MVC中,Controller 负责确定显示哪个 View 以响应任何操作,包括应用程序加载的时间。这与 MVP 不同,MVP 中的操作通过 View 路由到 Presenter。在 MVC 中,视图中的每个操作都与对控制器的调用以及操作相关联。在 Web 中,每个操作都涉及对 URL 的调用,在该 URL 的另一端有一个控制器进行响应。一旦该控制器完成其处理,它将返回正确的视图。该序列在应用程序的整个生命周期中以这种方式继续:
视图中的操作 -> 调用控制器 -> 控制器逻辑 -> 控制器返回视图。
MVC 的另一大区别是视图不直接绑定到模型。视图只是呈现并且是完全无状态的。在 MVC 的实现中,视图通常不会在后面的代码中包含任何逻辑。这与绝对必要的 MVP 相反,因为如果 View 不委托给 Presenter,它将永远不会被调用。
要查看的另一种模式是演示模型图案。在此模式中,没有 Presenter。相反,视图直接绑定到表示模型。演示模型是专门为视图制作的模型。这意味着该模型可以公开永远不会放在域模型上的属性,因为这将违反关注点分离。在这种情况下,Presentation Model 绑定到域模型并可能订阅来自该模型的事件。然后 View 订阅来自 Presentation Model 的事件并相应地更新自己。演示模型可以公开视图用于调用操作的命令。这种方法的优点是您可以从根本上完全删除代码隐藏,因为 PM 完全封装了视图的所有行为。模型-视图-视图模型。
有一篇关于表示模型的 MSDN 文章和WPF (前 Prism)的复合应用程序指南中关于分离表示模式的部分
这是对这些设计模式的许多变体的过度简化,但这就是我喜欢思考两者之间差异的方式。
MVC
MVP
不久前,我在博客上写了这篇文章,引用了Todd Snyder 关于两者区别的优秀文章:
以下是模式之间的主要区别:
MVP模式
- 视图与模型的耦合更松散。演示者负责将模型绑定到视图。
- 更容易进行单元测试,因为与视图的交互是通过接口进行的
- 通常以一对一的方式查看演示者地图。复杂视图可能有多个演示者。
MVC 模式
- 控制器基于行为,可以跨视图共享
- 可以负责确定显示哪个视图
这是我能在网上找到的最好的解释。
这是代表通信流程的插图
MVP不一定是 View 负责的场景(例如,参见 Taligent 的 MVP)。
不幸的是,人们仍然将其作为一种模式(负责视图)而不是反模式来宣传,因为它与“这只是一个视图”(实用程序员)相矛盾。“这只是一个视图”表明向用户显示的最终视图是应用程序的次要关注点。Microsoft 的 MVP 模式使 Views 的重用变得更加困难,并且方便地为 Microsoft 的设计人员提供了鼓励不良做法的借口。
坦率地说,我认为 MVC 的基本问题适用于任何 MVP 实现,并且差异几乎完全是语义上的。只要您遵循视图(显示数据)、控制器(初始化和控制用户交互)和模型(底层数据和/或服务)之间的关注点分离,那么您就可以获得 MVC 的好处. 如果您正在实现收益,那么谁真正关心您的模式是 MVC、MVP 还是监督控制器?唯一真正的模式仍然是 MVC,其余的只是它的不同风格。
考虑一下这篇非常激动人心的文章,它全面列出了许多这些不同的实现。您可能会注意到,它们基本上都在做同样的事情,但略有不同。
我个人认为 MVP 只是最近才作为一个吸引人的术语重新引入,以减少语义偏执者之间争论某事是否真正 MVC 或证明微软快速应用程序开发工具的合理性。在我的书中,这些原因都不能证明它作为一种单独的设计模式存在是合理的。
在大多数情况下,视图会创建其演示者。演示者将与模型交互并通过界面操作视图。视图有时会与演示者交互,通常是通过一些界面。这归结为实施;您希望视图调用演示者的方法还是希望视图具有演示者侦听的事件?归结为:视图了解演示者。视图委托给演示者。
控制器是根据一些事件/请求创建或访问的。然后控制器创建适当的视图并与模型交互以进一步配置视图。归结为:控制器创建和管理视图;视图是控制器的从属。视图不知道控制器。
MVC(模型视图控制器)
输入首先指向控制器,而不是视图。该输入可能来自与页面交互的用户,但也可能来自简单地将特定 url 输入浏览器。在任何一种情况下,它都是一个控制器,用于启动某些功能。Controller 和 View 之间是多对一的关系。这是因为单个控制器可能会根据正在执行的操作选择不同的视图来呈现。注意从控制器到视图的单向箭头。这是因为视图不了解或引用控制器。控制器确实传回了模型,因此在视图和传递给它的预期模型之间存在知识,但提供它的控制器却没有。
MVP(模型视图展示器)
输入从 View 开始,而不是 Presenter。View 和关联的 Presenter 之间存在一对一的映射。View 持有对 Presenter 的引用。Presenter 还对从 View 触发的事件做出反应,因此它知道与之关联的 View。Presenter 根据它对模型执行的请求操作更新视图,但视图不知道模型。
更多参考
这个问题有很多答案,但我觉得需要一些非常简单的答案来清楚地比较两者。这是我在用户在 MVP 和 MVC 应用程序中搜索电影名称时编造的讨论:
用户:点击点击……</p>
观点:那是谁?[最有价值球员| MVC ]
用户:我刚刚点击了搜索按钮……</p>
观点:好的,等一下…… [最有价值球员| MVC ]
(视图调用Presenter | Controller ...) [ MVP | MVC ]
视图:嘿主持人| 控制器,一个用户刚刚点击了搜索按钮,我该怎么办?[最有价值球员| MVC ]
主持人| 控制器:嘿视图,那个页面上有搜索词吗?[最有价值球员| MVC ]
View : 是的,……这里是……“钢琴” [ MVP | MVC ]
主持人| 控制器:谢谢View,... 同时我正在查找模型上的搜索词,请给他/她显示一个进度条 [ MVP | MVC ]
(Presenter | Controller正在调用模型……) [ MVP | MVC ]
主持人| 控制器:嘿模型,你有这个搜索词匹配吗?:“钢琴” [ MVP | MVC ]
型号:嘿主持人| 控制器,让我检查... [ MVP | MVC ]
(模型正在对电影数据库进行查询……) [ MVP | MVC ]
( 过了一会儿 ... )
-------------- 这就是 MVP 和 MVC 开始分歧的地方 ---------------
型号:我为您找到了一个列表,Presenter,这里是 JSON “[{"name":"Piano Teacher","year":2001},{"name":"Piano","year":1993} ]” [ MVP ]
模型:有一些可用的结果,控制器。我在我的实例中创建了一个字段变量并用结果填充它。它的名字是“searchResultsList”[ MVC ]
(Presenter | Controller感谢Model并返回View) [ MVP | MVC ]
主持人:感谢您等待查看,我为您找到了匹配结果列表,并将它们排列成一个像样的格式:[“钢琴老师2001”,“钢琴1993”]。请在垂直列表中显示给用户。也请现在隐藏进度条[ MVP ]
控制器:感谢您等待View,我已经向Model询问了您的搜索查询。它说它找到了一个匹配结果列表并将它们存储在其实例内名为“searchResultsList”的变量中。你可以从那里得到它。也请现在隐藏进度条 [ MVC ]
观点:非常感谢Presenter [ MVP ]
视图:谢谢“控制器”[ MVC ](现在视图在质疑自己:我应该如何将我从模型中获得的结果呈现给用户?电影的制作年份应该排在第一个还是最后一个......?应该在垂直或水平列表中?...)
如果您有兴趣,我一直在写一系列关于应用程序架构模式(MVC、MVP、MVVP、干净架构等)的文章,并附有一个 Github 存储库。即使该示例是为 android 编写的,其基本原理也可以应用于任何媒体。
MVC = 模型-视图-控制器
另外值得记住的是,MVP 也有不同类型。Fowler 将模式分为两种——被动视图和监督控制器。
使用被动视图时,您的视图通常会实现一个细粒度的界面,其属性或多或少直接映射到底层 UI 小部件。例如,您可能有一个具有名称和地址等属性的 ICustomerView。
您的实现可能如下所示:
public class CustomerView : ICustomerView
{
public string Name
{
get { return txtName.Text; }
set { txtName.Text = value; }
}
}
您的 Presenter 类将与模型对话并将其“映射”到视图。这种方法称为“被动视图”。好处是视图易于测试,并且更容易在 UI 平台(Web、Windows/XAML 等)之间移动。缺点是您不能利用数据绑定之类的东西(这在WPF和Silverlight等框架中非常强大)。
MVP 的第二种风格是监督控制器。在这种情况下,您的 View 可能有一个名为 Customer 的属性,然后再次将其数据绑定到 UI 小部件。您不必考虑同步和微观管理视图,监督控制器可以在需要时介入并提供帮助,例如复杂的交互逻辑。
MVP 的第三种“风格”(或者有人可能称其为单独的模式)是 Presentation Model(或有时称为 Model-View-ViewModel)。与 MVP 相比,您将 M 和 P “合并”为一个类。您有您的 UI 小部件数据绑定到的客户对象,但您也有额外的 UI 特定字段,如“IsButtonEnabled”或“IsReadOnly”等。
我认为我发现的关于 UI 架构的最佳资源是 Jeremy Miller 在The Build Your Own CAB Series Table of Contents上完成的一系列博客文章。他涵盖了 MVP 的所有风格,并展示了 C# 代码来实现它们。
我还在YouCard Re-visited: Implementing the ViewModel pattern上发表了关于 Silverlight 上下文中的 Model-View-ViewModel 模式的博客。
这两个框架都旨在分离关注点——例如,与数据源(模型)、应用程序逻辑(或将这些数据转化为有用信息)(控制器/演示器)和显示代码(视图)的交互。在某些情况下,该模型还可用于将数据源转换为更高级别的抽象。MVC Storefront 项目就是一个很好的例子。
这里有一个关于 MVC 与 MVP 之间差异的讨论。
区别在于,在 MVC 应用程序中,视图和控制器传统上与模型交互,但彼此之间不交互。
MVP 设计让 Presenter 访问模型并与视图交互。
话虽如此,ASP.NET MVC 根据这些定义是一个 MVP 框架,因为控制器访问模型以填充视图,这意味着没有逻辑(仅显示控制器提供的变量)。
要了解 ASP.NET MVC 与 MVP 的区别,请查看Scott Hanselman 的MIX 演示文稿。
两者都是试图分离表示和业务逻辑的模式,将业务逻辑与 UI 方面解耦
在架构上,MVP 是基于页面控制器的方法,而 MVC 是基于前端控制器的方法。这意味着在 MVP 标准 Web 表单中,页面生命周期只是通过从后面的代码中提取业务逻辑来增强。换句话说,页面是服务于 http 请求的一个。换句话说,MVP 恕我直言是网络表单进化类型的增强。另一方面,MVC 完全改变了游戏,因为请求在页面加载之前被控制器类拦截,业务逻辑在那里执行,然后在控制器处理刚刚转储到页面(“视图”)的数据的最终结果中从某种意义上说,MVC 看起来(至少在我看来)很像使用路由引擎增强的 MVP 的监督控制器风格
它们都启用了 TDD,并且各有优缺点。
恕我直言,如何选择其中之一的决定应基于一个人在 ASP NET Web 表单类型的 Web 开发上投入了多少时间。如果有人认为自己擅长 Web 表单,我会建议 MVP。如果人们在页面生命周期等方面感觉不太舒服,那么 MVC 可能是一种方法。
这是另一个博客文章链接,提供有关此主题的更多详细信息
我使用过 MVP 和 MVC,虽然我们作为开发人员倾向于关注这两种模式的技术差异,但 IMHO 中 MVP 的重点更多地与易于采用有关。
如果我在一个已经在 Web 表单开发风格方面具有良好背景的团队工作,那么引入 MVP 比 MVC 容易得多。我会说这种情况下的 MVP 是一个快速的胜利。
我的经验告诉我,将团队从 Web 表单转移到 MVP,然后从 MVP 转移到 MVC 相对容易;从 Web 表单迁移到 MVC 更加困难。
我在这里留下了一个链接,指向我的一个朋友发表的关于 MVP 和 MVC 的一系列文章。
http://www.qsoft.be/post/Building-the-MVP-StoreFront-Gutthrie-style.aspx
在 MVP 中,视图从演示者中提取数据,演示者从模型中提取和准备/规范化数据,而在 MVC 中,控制器通过视图中的推送从模型中提取数据并设置。
在 MVP 中,您可以让单个视图与多种类型的演示者一起使用,而单个演示者可以与不同的多个视图一起使用。
MVP 通常使用某种绑定框架,例如 Microsoft WPF 绑定框架或 HTML5 和 Java 的各种绑定框架。
在这些框架中,UI/HTML5/XAML 知道每个 UI 元素显示的演示者的哪些属性,因此当您将视图绑定到演示者时,视图会查找属性并知道如何从它们中提取数据以及如何当用户在 UI 中更改值时设置它们。
因此,例如,如果模型是汽车,那么演示者就是某种汽车演示者,将汽车属性(年份、制造商、座位等)暴露给视图。视图知道名为“汽车制造商”的文本字段需要显示演示者制造商属性。
然后,您可以将许多不同类型的演示者绑定到视图,所有演示者都必须具有 Maker 属性 - 它可以是飞机、火车或其他任何东西,视图无关紧要。视图从演示者那里获取数据——不管是哪一个——只要它实现了一个约定的接口。
这个绑定框架,如果你把它拆掉,它实际上是控制器:-)
因此,您可以将 MVP 视为 MVC 的演变。
MVC 很棒,但问题在于它通常是每个视图的控制器。控制器 A 知道如何设置视图 A 的字段。如果现在,您希望视图 A 显示模型 B 的数据,您需要控制器 A 知道模型 B,或者您需要控制器 A 接收具有接口的对象 - 这就像MVP 仅没有绑定,或者您需要在 Controller B 中重写 UI 集代码。
结论 - MVP 和 MVC 都是 UI 模式的解耦,但 MVP 通常使用绑定框架,其底层是 MVC。因此,MVP 处于比 MVC 更高的架构级别,并且是 MVC 之上的包装器模式。
我谦虚的短视:MVP 用于大规模,MVC 用于小规模。使用 MVC,我有时会觉得 V 和 C 可能被视为单个不可分割组件的两侧,而不是直接绑定到 M,而当缩小到更短的比例时,如 UI 控件和基本小部件,不可避免地会落入这一点。在这种粒度级别上,MVP 没有什么意义。相反,当一个更大的规模时,适当的接口变得更加重要,与明确的职责分配相同,MVP 出现了。
另一方面,当平台特性有利于组件之间的某种关系时,这种规模经验法则的重要性可能很小,比如在 Web 中,MVC 似乎比 MVP 更容易实现。
最简单的答案是视图如何与模型交互。在 MVP 中,视图由演示者更新,演示者充当视图和模型之间的中介。演示者从视图中获取输入,从模型中检索数据,然后执行所需的任何业务逻辑,然后更新视图。在 MVC 中,模型直接更新视图,而不是通过控制器返回。
Bob 叔叔有一段很棒的视频,最后他简要解释了MVC和MVP。
IMO,MVP 是 MVC 的改进版本,您基本上将要显示的内容(数据)与要显示的方式(视图)分开。Presenter 包含了你的 UI 的业务逻辑,隐含地强加了应该呈现的数据,并给你一个哑视图模型列表。当需要显示数据时,您只需将视图(可能包含相同的 id)插入适配器并使用那些视图模型设置相关的视图字段,并引入最少的代码(仅使用 setter)。它的主要好处是您可以针对许多/各种视图测试您的 UI 业务逻辑,例如在水平列表或垂直列表中显示项目。
在 MVC 中,我们通过接口(边界)来粘合不同的层。控制器是我们架构的插件,但它没有这样的限制来强加显示什么。从这个意义上说,MVP 是一种 MVC,其概念是视图可通过适配器插入控制器。
我希望这有助于更好。
您忘记了Action-Domain-Responder ( ADR )。
如上图所示,MVC 中的模型和视图之间存在直接关系/链接。在Controller上执行一个动作,这将在Model上执行一个动作。模型中的那个动作,会触发视图中的反应。View总是在Model的状态发生变化时更新。
有些人一直忘记,MVC是在 70 年代末创建的,而 Web 只是在 80 年代末/90 年代初创建的。MVC 最初不是为 Web 创建的,而是为桌面应用程序创建的,其中控制器,模型和视图将共存。
因为我们使用的 Web 框架(例如:.Laravel)仍然使用相同的命名约定(model-view-controller),所以我们倾向于认为它一定是 MVC,但实际上它是另外一回事。
相反,请查看Action-Domain-Responder。在 ADR 中,Controller获得一个Action,它将在Model/Domain中执行操作。到目前为止,相同。不同之处在于,它然后收集该操作的响应/数据,并将其传递给响应程序(例如:.view()
)进行渲染。当在同一个组件上请求新的操作时,控制器会再次被调用,并且循环会重复。在 ADR 中,模型/域和视图(响应者的响应)之间没有任何联系。
注意: Wikipedia 声明“然而,每个 ADR 操作都由单独的类或闭包表示。 ”。这不一定是真的。多个Action可以在同一个Controller中,模式还是一样的。
在 MVC 中,Controller 是负责人!控制器根据一些事件/请求被触发或访问,然后管理视图。
MVC 中的视图实际上是无状态的,控制器负责选择要显示的视图。
例如:当用户点击“Show MyProfile”按钮时,Controller 被触发。它与模型通信以获取适当的数据。然后,它会显示一个类似于配置文件页面的新视图。控制器可以从模型中获取数据并将其直接提供给视图(如上图所示),或者让视图从模型本身获取数据。
在 MVP 中,视图是负责人!每个 View 调用它的 Presenter 或者有一些 Presenter 监听的事件。
MVP 中的视图不实现任何逻辑,Presenter 负责实现所有逻辑并使用某种接口与视图通信。
例如:当用户单击“Save”按钮时,View 中的事件处理程序将委托给 Presenter 的“OnSave”方法。Presenter 将执行所需的逻辑并与模型进行任何所需的通信,然后通过其接口回调视图,以便视图可以显示保存已完成。
简单来说,
MVP
MVP 代表模型 - 视图 - 演示者。这出现在 2007 年初,微软推出了 Smart Client Windows 应用程序。
演示者在 MVP 中充当监督角色,将视图事件和模型中的业务逻辑绑定在一起。
视图事件绑定将在 Presenter 中从视图界面实现。
视图是用户输入的发起者,然后将事件委托给 Presenter,Presenter 处理事件绑定并从模型中获取数据。
优点: 视图只有 UI 没有任何逻辑 高水平的可测试性
缺点: 实现事件绑定时有点复杂且工作量更大
MVC
MVC 代表模型-视图-控制器。Controller 负责创建模型并使用绑定模型渲染视图。
控制器是发起者,它决定渲染哪个视图。
优点: 强调单一职责原则 高水平的可测试性
缺点: 如果尝试在同一个控制器中渲染多个视图,有时控制器的工作量太大。