MVP和MVC是什么,它们有什么区别?

2360

当我们超越许多工具鼓励的RAD(拖放和配置)方式构建用户界面时,您可能会遇到三种设计模式,称为Model-View-Controller, Model-View-PresenterModel-View-ViewModel。我的问题分为三个部分:

  1. 这些模式解决了哪些问题?
  2. 它们有什么相似之处?
  3. 它们有什么不同之处?

6
http://mvc.givan.se/#mvp - givanse
3
我不确定,但据说对于最初的MVC,它是用于小型应用程序的。每个按钮、标签等都有自己的视图和控制器对象,或者至少这是Uncle Bob所声称的。我认为他在谈论Smalltalk。在YouTube上查找他的演讲,它们非常有趣。 - still_dreaming_1
1
MVP 将 View-Controller 拆分为 View 和 Presenter,通过添加额外的间接层实现。 - the_prole
4
主要的区别在于MVC中,控制器不会将任何数据从模型传递到视图。它只通知视图自己从模型获取数据。然而,在MVP中,视图和模型之间没有连接。Presenter本身从模型获取所需的任何数据,并将其传递给视图以显示。更多关于所有架构模式的Android示例可以在这里找到:http://www.digigene.com/category/android/android-architecture/ - Ali Nem
1
它们被称为“架构模式”而不是“设计模式”。如果您想了解区别,请查看此链接:https://dev59.com/VG855IYBdhLWcg3wnFtO - Hasan El-Hefnawy
24个回答

2127

模型-视图-控制器

MVP中,Presenter包含了视图的UI业务逻辑。所有来自视图的调用都直接委托给Presenter。Presenter也直接与视图解耦,并通过接口与其通信。这是为了允许在单元测试中对视图进行模拟。MVP的一个常见特征是必须有大量的双向分派。例如,当有人点击“保存”按钮时,事件处理程序将委托给Presenter的"OnSave"方法。一旦保存完成,Presenter将通过其接口回调视图,以便视图可以显示保存已完成。

MVP往往是在WebForms中实现分离呈现的一种非常自然的模式。原因是视图始终是由ASP.NET运行时首先创建的。您可以find out more about both variants

两个主要变体

被动视图:视图尽可能地愚笨,几乎不包含任何逻辑。Presenter是一个中间人,负责与视图和模型交互。视图和模型完全隔离。模型可能会引发事件,但Presenter订阅它们以更新视图。在被动视图中,没有直接的数据绑定,相反,视图公开setter属性,Presenter使用这些属性设置数据。所有状态都由Presenter管理,而不是视图。

  • 优点:最大化可测试性表面;视图和模型之间的清晰分离。
  • 缺点:更多工作(例如所有setter属性),因为您需要自己进行所有数据绑定。

监控控制器:Presenter处理用户手势。视图通过数据绑定直接绑定到模型。在这种情况下,Presenter的工作是将模型传递给视图,以便它可以与之绑定。Presenter还将包含有关手势(如按按钮,导航等)的逻辑。

  • 优点:通过利用数据绑定,代码量减少了。
  • 缺点:由于数据绑定,测试表面较小,并且视图中的封装性较低,因为它直接与模型交互。

模型-视图-控制器

MVC中,控制器负责确定响应任何操作(包括应用程序加载时)显示哪个视图。这与MVP不同,MVP中的操作通过视图路由到Presenter。在MVC中,视图中的每个操作都与对控制器的调用及其操作相关联。在Web中,每个操作涉及对另一侧URL的调用,在该URL的另一侧有一个响应的控制器。一旦该控制器完成其处理,它将返回正确的视图。该序列在应用程序的整个生命周期中继续进行:

    视图中的操作
        -> 调用控制器
        -> 控制器逻辑
        -> 控制器返回视图。

MVC的另一个重要区别是View不直接绑定到Model。View只是简单地渲染,完全没有状态。在MVC的实现中,View通常不会在代码后面有任何逻辑。这与MVP相反,因为如果View不委托给Presenter,它将永远不会被调用。

展示模型

另一个要看的模式是展示模型模式。在这个模式中,没有Presenter。相反,View直接绑定到展示模型。展示模型是专门为View设计的Model。这意味着该模型可以公开一些您永远不会放在域模型上的属性,因为这将违反关注点分离原则。在这种情况下,展示模型绑定到域模型,并可能订阅来自该模型的事件。然后,View订阅来自展示模型的事件,并相应地更新自己。展示模型可以公开命令,View用于调用操作。这种方法的优点是您可以完全删除代码后面,因为PM完全封装了View的所有行为。此模式非常适合用于WPF应用程序,并称为Model-View-ViewModel

有一篇关于演示模型的MSDN文章,以及WPF复合应用指南(前身为Prism)中的一个章节,介绍了分离呈现模式


39
请问您能否澄清一下这句话的意思?“这与 MVP 不同,MVP 中的操作通过 View 到 Presenter 进行路由。在 MVC 中,视图中的每个操作都与控制器以及一个动作调用相关联。” 对我来说听起来是一样的,但我肯定你描述的是不同的东西。 这句话的意思是,在MVP模式中,用户动作通过View传递到Presenter进行处理,而在MVC模式中,每个View中的动作都需要与一个Controller一起被调用和处理。虽然两种模式都有类似的结构,但它们的实现方式略有不同。 - Panzercrisis
18
@Panzercrisis 我不确定这是否是作者的意思,但我认为他们试图表达的是:就像这个答案 https://dev59.com/h3VD5IYBdhLWcg3wXacd#2068 所提到的,在MVC中,控制器方法基于行为——换句话说,您可以将多个视图(但相同的行为)映射到单个控制器。在MVP中,Presenter更紧密地耦合到View,并且通常结果是一对一的映射,即一个视图操作映射到其对应的Presenter方法。通常,您不会将另一个视图的操作映射到另一个Presenter的(来自另一个视图的)方法。 - Dustin Kendall
4
请注意,例如 Laravel 这样的 Web 框架经常使用 MVC 架构来处理接收到的 URL 请求(可能是用户发起的),请求由 Controller 处理,View 生成的 HTML 将发送给客户端。因此,View 是后端的一部分,用户无法直接访问它。如果您在任何地方遇到相反的情况,请将其视为 MVC 扩展(甚至是违规行为)。这与 MVP 不同(例如 Android 操作系统中使用的 MVP),其中操作通过 View 路由到 Presenter,用户可以直接访问 View。 - Top-Master
6
作者所描述的MVC并不是原始的Smalltalk MVC(其流程为三角形),而是指“Web MVC”,其中控制器使用模型呈现视图并将其返回给用户。我认为这值得注意,因为这会引起很多混淆。 - raiks
Presentation Model (MVVM)和MVP Passive View (MVP-PV)之间有没有根本的区别?1. MVP-PV: "View公开setter属性,Presenter使用这些属性来设置数据",MVVM: "然后View订阅来自Presentation Model的事件,并相应地更新自己" - 信息流相同,实现细节有所不同。2. "Presentation Model是专门为View设计的Model" - 就像在MVP Passive View中一样(View是哑的,所以Presentation Model必须为其设计)。 - undefined

516

这只是这些设计模式的许多变体的简化,但这是我喜欢思考两者之间差异的方式。

MVC

MVC

MVP

enter image description here


12
这是一个很好的示意图,展示了GUI相关(视图部分)与Presenter API的完全隔离和抽象。一个小问题是,当只有一个Presenter时,可以使用一个主Presenter,而不是每个视图都有一个Presenter,但你的图表是最清晰的。在我看来,MVC/MVP之间最大的区别在于,MVP试图使视图完全没有除了显示当前'视图状态'(视图数据)之外的任何东西,并且不允许视图了解Model对象。因此,这些接口需要存在,以注入该状态。 - user2080225
4
好的图片。我经常使用MVP,所以我想提出一点。根据我的经验,Presenter之间需要经常交流。对于Models(或业务对象)也是如此。由于你的MVP图片中会有额外的“蓝色通信线”,Presenter-Model关系可能会变得非常复杂。因此,我倾向于保持一对一的Presenter-Model关系,而不是一对多。是的,这需要在Model上添加一些额外的代理方法,但如果Model的API发生更改或需要重构,它可以减少许多头痛。 - splungebob
4
MVC 示例有误;视图和控制器之间存在严格的一对一关系。按照定义,一个控制器解释人类手势输入以产生单个控件模型和视图都能使用的事件。更简单地说,MVC 仅适用于单个小部件。一个小部件,一个视图,一个控件。 - Samuel A. Falvo II
3
@SamuelA.FalvoII,并非总是如此,ASP.NET MVC中的控制器和视图之间存在一对多的关系:https://dev59.com/8XI-5IYBdhLWcg3wy7sd - StuperUser
6
@StuperUser -- 当时写这句话的时候可能有些糊涂了。你说得没错,回过头来看我写的东西,我不禁想知道当时是否有其他上下文我没有表达清楚。感谢你的纠正。 - Samuel A. Falvo II
显示剩余6条评论

447
我之前发表过一篇关于这个问题的博客,并引用了Todd Snyder在他出色的文章中谈到的两者区别

以下是模式之间的主要区别:

MVP模式

  • 视图与模型之间的耦合性较弱。Presenter负责将模型与视图绑定。
  • 易于单元测试,因为与视图的交互是通过接口进行的。
  • 通常视图到Presenter呈一对一映射。复杂视图可能有多个Presenter。

MVC模式

  • 控制器基于行为,可以在多个视图中共享。
  • 可以负责确定显示哪个视图。
这是我能找到的最好的解释。

19
我不明白在视图和模型都旨在完全解耦的情况下,如何可以将二者联系得更紧密或更松散。我并不意味着你说错了什么——只是不清楚你的意思。 - Bill K
20
使用 MVP 模式,每个视图都对应一个 Presenter。而在 MVC 模式下,控制器可以管理多个视图。就是这样。在“标签页”模型中,可以想象每个选项卡都有自己的 Presenter,而不是所有选项卡共用一个控制器。 - Jon Limjap
4
最初,有两种类型的控制器:一种是你所说的可以在多个视图之间共享的控制器,另一种是特定于视图的控制器,主要用于适应共享控制器的界面。 - Acsor
1
@JonLimjap 一个视图是什么意思呢?在iOS编程的背景下,它是指一个屏幕吗?这是否使得iOS的控制器更像MVP而不是MVC?(另一方面,您也可以在一个屏幕上拥有多个iOS控制器) - huggie
2
Todd的MVC图示完全与解耦视图和模型的思想相矛盾。如果你看一下这个图,它写着模型更新视图(从模型到视图的箭头)。在哪个世界里,模型直接与视图交互的系统是一个解耦的系统? - Ash
显示剩余3条评论

284
这是表示通信流程的插图。

图片描述

图片描述

50
关于MVC图示,我有一个问题。我不理解视图获取数据的那一部分。我认为控制器应该将需要的数据转发给视图。 - Brian Rizo
71
如果用户点击一个按钮,那么这不是在与视图交互吗?我觉得在MVC中,用户与视图的交互比与控制器的交互更多。 - Jonathan
5
我知道这是一个旧回答,但是否有人可以回复 @JonathanLeaders 的观点?我来自 WinForms 背景,除非你进行了一些非常独特的编码,否则当你点击 UI/View 时,UI/View 会在其他任何东西之前获得该点击事件的知识。至少就我所知? - Rob P.
7
@RobP. 我认为这类图表往往要么过于复杂,要么过于简单。在我看来,MVP图表的流程也适用于MVC应用程序。根据语言特性(数据绑定/观察者),可能会有一些变化,但最终的想法是将视图与应用程序的数据/逻辑解耦。 - Luca Fülbier
24
当人们谈论“MVC”时,实际上可能有非常不同的理解。创建这张图表的人可能是在考虑传统的Web MVC模式,其中“用户输入”是HTTP请求,“返回给用户的视图”是已呈现的HTML页面。因此,从古典Web MVC应用程序的作者的角度来看,用户和视图之间的任何交互都是“不存在的”。请注意,此翻译保留了原始含义并尽可能地使其通俗易懂。 - cubuspl42
显示剩余7条评论

187

MVP并不一定是View负责的场景(例如Taligent的MVP)。我认为仍有人把这作为一种模式(View负责)而非反模式,这与“它只是一个视图”(Pragmatic Programmer)相矛盾。 “它只是一个视图”表示向用户显示的最终视图是应用程序的次要关注点。Microsoft的MVP模式使得视图的重复使用更加困难,并方便地为Microsoft的设计师辩护,以免鼓励不良实践。

坦白地说,我认为MVC的基本问题对于任何MVP实现都是正确的,差异几乎完全是语义上的。只要在视图(显示数据)、控制器(初始化和控制用户交互)和模型(底层数据和/或服务)之间遵循关注点分离,就可以实现MVC的好处。如果您正在实现这些好处,则无论您的模式是MVC、MVP还是Supervising Controller,都没有太大关系。唯一真正的模式仍然是MVC,其余只是不同的风味。

请查看非常令人兴奋的文章,其中详细列出了许多这些不同的实现。您可能会注意到它们基本上都在以稍微不同的方式做着相同的事情。

我个人认为,MVP只是最近作为一个流行术语重新引入,以减少那些争论某些东西是否真正符合MVC标准的人之间的争吵,或者为Microsoft的快速应用程序开发工具辩护。在我看来,这两个原因都不能证明MVP作为一种单独的设计模式的存在。


33
我读了很多关于MVC/MVP/MVVM等模式之间差异的答案和博客。实际上,当你真正开始做业务时,它们都是一样的。有没有接口,是否使用setter(或任何其他语言特性)并不重要。看起来,这些模式之间的差异源于各种框架实现的不同,而不是概念上的区别。 - Michael
6
我不认为 MVP 是一种反模式(anti-pattern),因为在文章的后面写道:“..其余的[包括MVP]只是[MVC]的不同风味..”,这意味着如果MVP是反模式,那么MVC也是...它只是一个不同框架方法的变体。(现在,某些特定的MVP实现可能比某些特定的MVC实现更或更少适合不同的任务...) - user166390
@Quibblsome:“我个人认为MVP只是最近重新引入的一个流行术语,旨在减少语义狂热者之间争论某些东西是否真正符合MVC的争议[...]在我的书中,这些原因都不能证明它作为一个单独的设计模式的存在。” 它有足够的差异使其独特。在MVP中,视图可以是任何满足预定义接口的东西(MVP中的视图是一个独立的组件)。在MVC中,控制器是为特定视图而制作的(如果关系的度数让某人觉得值得另一个术语)。 - Hibou57
6
@Hibou57,MVC 模式没有任何阻止使用视图作为接口或创建泛用控制器来处理多个不同视图的方式。 - Quibblesome
@quibblesome 实际上是有的。控制器与其对应的视图紧密绑定,因为它们的工作是将人类手势(按键、鼠标更新等)解释为窗口上各个控件的事件。这就是为什么你必须严格遵循一个控制器对应一个视图的关系。控制器从来没有被用于整个表单的使用。为了实现整个表单的使用,应用程序模型(又称演示模型)应运而生,更适合这个目的。由于非Smalltalk GUI不依赖MVC来实现控件,所以在实践中MVC相对没有太多意义。 - Samuel A. Falvo II
2
Samuel,请澄清你所说的内容。除非你在讲述“发明”MVC的团队的历史,否则我对你的文字非常怀疑。如果你只是在谈论WinForm,那么还有其他方法可以做到这一点,我创建了WinForm项目,其中控件绑定由控制器管理,而不是“单个控件”。 - Quibblesome

123

MVP:视图掌控一切

在大多数情况下,视图会创建其自身的 presenter。Presenter 将与模型交互,并通过接口操作视图。视图有时会通过接口与 presenter 进行交互,通常情况下是这样的。具体实现上,你是想让视图调用 presenter 的方法呢,还是想让视图拥有 presenter 监听的事件?总之就是这样:视图知道 presenter 的存在,因此视图委托给了 presenter。

MVC:控制器掌控一切

基于某些事件/请求,控制器被创建或访问。然后,控制器创建适当的视图,并与模型交互以进一步配置视图。简而言之,控制器创建和管理视图;视图受控制器支配。视图不知道控制器的存在。


3
“View does not know about Controller.” 我认为你的意思是视图与控制器没有直接联系,是吗? - Lotus Notes
2
视图不应该在任何情况下了解模型。 - Brian Leahy
5
@Brian说:“在大多数情况下,View会创建它的Presenter。”但我通常看到的是相反的情况,即Presenter启动Model和View。当然,View也可能启动Presenter,但这一点并不是最突出的。最重要的是在后续的生命周期中发生了什么。 - Hibou57
2
你可能需要编辑你的回答以进一步解释:由于视图不知道控制器的存在,那么用户在屏幕上看到的“可视”元素(即视图)上执行的操作如何传达给控制器... - Ash

85

enter image description here

MVC (模型-视图-控制器)

输入先经过控制器,而不是视图。这个输入可能来自用户与页面交互,但也可能只是在浏览器中输入特定的 URL。无论哪种情况,都是通过控制器接口启动某些功能。

控制器与视图之间存在多对一的关系。这是因为单个控制器可以选择不同的视图进行渲染,具体取决于正在执行的操作。

请注意从控制器到视图的单向箭头。这是因为视图没有任何控制器的知识或引用。

控制器确实会传回模型,因此视图了解并期望接收到的模型,但不了解提供它的控制器。

MVP(模型-视图-表示器)

输入始于视图,而不是表示器。

视图与相关表示器之间有一对一的映射关系。

视图保存对表示器的引用。表示器还会响应从视图触发的事件,因此它知道与其相关联的视图。

表示器根据其对模型执行的请求操作更新视图,但视图不了解模型。

更多参考:http://geekswithblogs.net/dlussier/archive/2009/11/21/136454.aspx


但是在MVP模式中,当应用程序首次加载时,不是由Presenter负责加载第一个视图吗?例如,当我们加载Facebook应用程序时,Presenter难道不是负责加载登录页面的吗? - viper
2
在MVC中,从模型到视图的链接?您可能希望编辑您的答案以解释这如何使它成为一个“解耦”系统,考虑到这个链接。提示:您可能会发现这很困难。此外,除非您认为读者愿意接受他们整个生活都在计算错误,否则您可能需要详细说明为什么在MVC中,尽管用户与屏幕上的“可视”元素(即视图)进行交互,但操作首先通过控制器进行,而不是某个抽象的后台处理层。 - Ash
5
在MVC中,模型永远不会直接与视图交互,反之亦然。它们甚至不知道对方的存在。控制器是将它们粘合在一起的纽带。 - MegaManX
1
我同意Ash和MegaManX的观点。在MVC图中,箭头应该从View指向Model(或ViewModel或DTO),而不是从Model指向View;因为Model不知道View,但View可能知道Model。 - Jeremiah Flaga
1
实际上,我认为基于原始的SmallTalk三角形MVC,模型-视图链接是正确的:https://commons.wikimedia.org/wiki/File:MVC-Process.svg#/media/File:MVC-Process.svg。我看到的问题是控制器的输入及其与视图的链接*到*。通常用户与视图交互,因此视图应该链接到控制器。 - Mahm00d
@JeremiahFlaga 和 MegaManX。在MVC中,模型可以通过观察者模式与视图进行交互。因为它们之间有接口,所以这并不重要。但是,模型并不知道视图的存在。此外,箭头应该从模型指向视图,因为模型通过观察者接口通知视图。 - rosshjb

82

对于这个问题,有很多答案,但我认为需要一些真正简单的答案来清晰比较这两种模式。以下是当用户在MVP和MVC应用程序中搜索电影名称时,我编写的讨论:

用户:点击点击...

视图:谁啊?[MVP|MVC]

用户:我刚才点击了搜索按钮…

视图:好的,请稍等...[MVP|MVC]

(视图调用Presenter|Controller...) [MVP|MVC]

视图:嘿 Presenter|Controller,一个用户刚刚点击了搜索按钮,我该怎么办?[MVP|MVC]

Presenter|Controller:嘿 View,这个页面上有任何搜索术语吗?[MVP|MVC]

视图:有,“piano”就在这里[MVP|MVC]

Presenter|Controller:谢谢View,...与此同时,我正在模型上查找搜索术语,请向用户显示进度条[MVP|MVC]

( Presenter|Controller在调用Model...) [MVP|MVC]

Presenter|Controller:嘿Model,你有这个搜索术语的任何匹配项吗?:“piano”[MVP|MVC]

模型:嘿,展示者|控制器,让我检查一下……[MVP|MVC]

模型正在查询电影数据库……)[MVP|MVC]

(过了一会儿……)

-------------- 这是MVP和MVC开始分歧的地方 ---------------

模型:我为你找到了一个列表,展示者,这是它的JSON格式“[{"name":"钢琴教师","year":2001},{"name":"钢琴","year":1993}]”[MVP]

模型:有一些可用的结果,控制器。我在我的实例中创建了一个字段变量,并将其填充了结果。它的名称是“searchResultsList”[MVC]

展示者|控制器感谢模型并回到视图)[MVP|MVC]

展示者:感谢你等待,视图。我为你找到了一系列匹配结果,并以可呈现的格式整理好了:“["钢琴教师 2001","钢琴 1993"]”。请在垂直列表中向用户展示它。同时请隐藏进度条[MVP]

控制器:感谢你等待,视图。我询问了模型关于你的搜索查询。它说已经找到了一系列匹配结果,并将它们存储在其实例内的名为“searchResultsList”的变量中。你可以从那里获取它。同时请隐藏进度条[MVC]

视图: 非常感谢你 主持人 [MVP]

视图: 谢谢你“控制器” [MVC] (现在,视图正在自问:我应该如何向用户呈现从模型获取的结果?电影的生产年份应该放在最前面还是最后面...?它应该是垂直列表还是水平列表? ...)

如果您有兴趣,我已经写了一系列关于应用程序架构模式(MVC、MVP、MVVP、清洁架构等)的文章,并附带一个Github库这里。尽管示例是为Android编写的,但基本原则可以适用于任何介质。


1
你的意思是控制器微观管理视图逻辑?因此,它通过在视图上呈现发生的事情和如何发生的方式使视图变得更加简单? - Radu
1
@Radu,不,它不会微观管理,这是演示者通过使视图被动或愚蠢来实现的。 - Ali Nem
8
在正确的MVC中,视图调用控制器上的功能,并监听模型中的数据变化。视图不从控制器获取数据,控制器也不应该告诉视图显示例如加载指示器之类的内容。正确的MVC允许您将视图部分替换为根本不同的部分。视图部分包含视图逻辑,其中包括加载指示器。视图调用指令(在控制器中),控制器修改模型中的数据,模型通知其数据变化的侦听器之一是视图。 - Tommy Andersen

44

模型-视图-控制器

MVC 是一种软件应用程序的架构模式。它将应用程序逻辑分成三个独立的部分,促进了模块化和协作重用。它还使应用程序更加灵活,容易进行迭代。它将一个应用程序分成以下组件:

  • 模型 用于处理数据和业务逻辑
  • 控制器 用于处理用户界面和应用程序
  • 视图 用于处理图形用户界面对象和演示文稿

为了让这更加清晰,让我们想象一个简单的购物清单应用程序。我们只需要一个列表,列出每个需要购买的项目的名称、数量和价格。下面我们将描述如何使用MVC实现其中一些功能。

enter image description here

模型-视图-表示器

  • 模型是将在视图(用户界面)中显示的数据。
  • 视图是一个接口,用于显示数据(即模型),并将用户命令(事件)路由到Presenter以对该数据进行操作。视图通常具有对其Presenter的引用。
  • Presenter是“中间人”(在MVC中扮演控制器的角色),并引用视图和模型。请注意,“模型”一词是具有误导性的。它应该更确切地是检索或操作模型的业务逻辑。例如:如果您的数据库存储了数据库表中的用户,并且您的视图希望显示用户列表,则Presenter将从DAO等业务逻辑引用中查询用户列表。

如果您想查看简单实现样例,请查看此处的GitHub帖子

从数据库查询和显示用户列表的具体工作流程可能像这样: enter image description here

MVCMVP模式之间的区别是什么?

MVC模式

  • 控制器基于行为,可以在视图之间共享

  • 可以负责确定显示哪个视图(前端控制器模式)

MVP模式

  • 视图与模型的耦合度更低。Presenter负责将模型绑定到视图上。

  • 更易于单元测试,因为与视图的交互是通过接口进行的

  • 通常视图与Presenter一一对应。复杂视图可能具有多个Presenter。


38
  • MVP = 模型-视图-展示器
  • MVC = 模型-视图-控制器

    1. 这两种模式都是面向演示的。它们将一个模型(想象一下领域对象)、您的屏幕/网页(视图)和 UI 应该如何行为(展示器/控制器)之间的依赖关系分离。
    2. 它们在概念上非常相似,人们根据个人喜好以不同的方式初始化 Presenter/Controller。
    3. 有一篇很棒的文章介绍了它们之间的区别,链接在这里。最值得注意的是,MVC 模式让模型更新视图。

3
模型更新视图。这仍然是一个解耦系统吗? - Ash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接