MVC:如何从控制器向视图传递模型/模型数据?

6
如果一个视图需要从模型中访问数据,您认为控制器应该:
a)将模型传递给视图 b)将模型的数据传递给视图 c)都不应该;这不应该是控制器的关注点。让视图直接访问模型以检索数据。只允许控制器提供一些参数,供视图过滤来自模型的数据。 d)这取决于情况。 e)以上都不是,但[...]
谢谢
在用户删除了一个答案的评论中进行了一些辩论后,也许需要澄清一下。我对MVC架构的看法偏向于Zend Framework(php),其中控制器中的操作默认情况下有一个默认视图分配给它。因此,决定合适的视图不是那么多由模型决定,而是由控制器决定。您是否认为模型应该决定哪个视图是适当的?我唯一能想到的让视图基于模型构建的方法是让控制器将模型传递给视图。是否存在其他技术可以让视图访问模型而不涉及控制器?或者让控制器将模型传递给视图以便基于模型属性构建视图是完全可以的吗?
7个回答

11

e) 以上答案均不是最佳选择,可以传递一个经过视图优化的 "ViewModel"。

在 ASP.NET MVC 中的示例:

public ActionResult Details(int id)
{
  Product p = ProductService.GetProductById(id);

  if(p == null) { return RedirectToAction("Index"); }

  ProductViewModel model = new ProductViewModel(p);
  return View(model);
}

在d)条件的限制下,a)和b)都可以做,但是c)绝对不要。

通常情况下,ViewModel仅封装Model(如果没有什么复杂的东西,你的View可以直接通过ProductViewModel.Product访问模型)。但是,如果视图需要针对Model执行任何复杂的操作,则由ViewModel负责处理,而不是由控制器或视图负责。

这样可以使你的关注点保持良好的隔离。您的控制器不关心视图需要哪些具体的数据(除了它正在渲染一些产品的详细信息),或者特别是视图需要什么格式的数据。您的视图不依赖于模型的实现细节。您的模型不必考虑它如何被查看。 如果您有两个显示产品的视图(例如Create、Edit、Summary、MoreDetails等),这些视图可以具有不同的ViewModels以仅公开每个特定视图所需的数据,因此您的视图不会相互依赖。美妙:)

以下是来自各种观点的进一步阅读:

http://www.thoughtclusters.com/2007/12/datamodel-and-viewmodel/

http://stephenwalther.com/blog/archive/2009/04/13/asp.net-mvc-tip-50-ndash-create-view-models.aspx

http://www.nikhilk.net/Silverlight-ViewModel-MVC.aspx

我认为ViewModels是一个特别的.NET东西,但我看不出为什么这种模式不能在PHP中使用。

希望对您有所帮助。


我非常喜欢这个想法。谢谢你。我会考虑如何将其纳入基于Zend框架的应用程序中。 但我仍然担心的是(也许您的View对象与ZF的View对象不同),控制器仍然需要决定应该呈现哪个视图。从纯MVC的角度来看,难道不应该由模型(或在您的情况下是ViewModel)决定正确呈现哪个视图吗?在您的示例中,它仍然看起来像控制器负责将正确的View与正确的ViewModel耦合。这个假设正确吗? - Decent Dabbler
如果您要根据模型的某些方面或请求的某些方面显示不同的视图,那么控制器绝对有责任选择要呈现的视图,并为该视图提供一些模型对象/数据。我不确定Zend如何处理它,如果它有一个中间层来进行选择,那么请坚持使用它。在ASP.NET MVC中,您只需指定要显示的视图的名称(例如,返回View(“Index”);),但需要遵循一些假设、约定和默认值。 - Iain Galloway
你可以查看一个关于在PHP中实现ViewModel的好教程:MVC-meet-the-viewmodel-pattern - Enrique

2

我知道有点晚了,但是我在处理一个项目时遇到了问题。一开始我选择了a) - 简单易懂,但现在却遇到了难题。

我正在尝试这种方法:

e) 不使用上述任何一种方式;而是传入一个视图优化的“ViewModel”。

因为这样可以避免让你的模型类(它们的实例是“transaction objects”)和视图变得过于臃肿。例如,你可能需要以特定的小数位数渲染数字(这正是我现在遇到的问题)。

有了中间层的“ViewModel”,这很容易实现 - 你只需编写相关的ViewModel “getXXX”方法,返回你想要的格式化后的数字即可。

如果你直接将模型传递给视图,每次使用该数字时都需要在视图中指定它(违反DRY原则 - 不要重复自己),或者在你的模型类中添加一个渲染方法(这违反了一个类只能有一个目的的原则)。

谢谢


2
理想情况下,应该“将模型的数据传递给视图”,这样视图就不需要知道任何显式的模型结构,从而更具可重用性和设计友好性。
但实际上,“将模型传递给视图”同样有效。大多数情况下,您仍然需要一个新的视图,因为客户永远不会共享喜欢的颜色(如果你知道我的意思:-),因此视图的可重用性并不能证明需要大量繁琐的代码来从模型复制数据到视图。
您应该更关注控制器本身的模块化,因为许多网站共享常见功能(控制器),如网络论坛或新闻列表,但外观(视图)却不同。

你不觉得这有点违反单一职责原则吗?如果你选择a)那么当Model的实现细节发生变化时,View也必须改变。如果你选择b)那么更糟糕的是,当Model或View发生变化时,Controller也必须改变。通过将将领域模型转换为适合View消费的格式的代码限制在ViewModel中,你可以将View与Model的变化隔离开来,并将Controller与任何变化隔离开来。 - Iain Galloway
@lain Galloway,我认为你没有理解我的观点。我的代码并没有进入控制器,我只是重用了模型类而没有使用“ViewModel”。记住,“代码必须放在某个地方”,这个地方已经在模型中了,所以大多数情况下添加“ViewModel”层就是代码复制/不合理的复杂性。更少的“代码量”。更少的“复杂性”,是的,稍微多一点“耦合”来换取这些。 - chakrit
@lain Galloway,如果过度使用,您最终会在Model和ViewModel之间产生大量的代码重复,导致维护上的噩梦...请参阅Jeff的这篇文章,以获取更多关于我的意思的解释:http://www.codinghorror.com/blog/archives/000878.html ... [最好的代码是没有代码] ... 不合理的复杂性=维护噩梦。 - chakrit
@Iain Galloway 又错过了我的重点...也许我应该放弃了。 - chakrit
@chakrit 我没有错过你的观点,我不同意它。与直接使用 Model 或让 Controller 提取数据相比,使用 ViewModel 可以减少复杂性。也许可以尝试不同的方法,你认为同样的论点适用于 DTO 吗?它们是否会导致“不合理的复杂性”?它们是否会导致“维护噩梦”?从我的经验来看,缺乏 DTO 才是导致噩梦的原因,而 ViewModel 的背后动机类似。 - Iain Galloway
显示剩余4条评论

1

a) 将模型传递给视图

否则,控制器通过筛选模型来操作视图。这就是在“b)将模型数据传递给视图”中会发生的事情。在纯MVC模式中,b)选项甚至没有意义。毕竟,模型就是数据。如果为了消费而更改模型,则已应用视图,无论您选择在控制器中执行并将其作为控制器函数传递。当您拥有不同的视图时会发生什么?控制器是否以不同方式筛选其数据?您很快就会为模型拥有两个视图,即控制器子视图和视图本身。


哦,关于c),控制是控制器的角色,d)不行,一致性是我们使用模式的原因,e)根本没有模式。 - dacracot
这是Reenskaug的文章...http://folk.uio.no/trygver/2003/javazone-jaoo/MVC_pattern.pdf...描述了他在1978年关于MVC的Xerox PARC工作。 - dacracot
正如我在Iain的回答中所评论的那样:我有点困惑。你们两个的答案似乎都假定控制器仍然负责将模型与正确的视图联系起来。这是正确的吗?还是你们的建议假定视图能够根据它接收到的模型确定应该使用哪个明确的视图(或模板)进行呈现?(更符合Zend框架ViewRenderer对象的要求;就像名称所示:一个渲染器,你可以告诉它应该呈现哪个视图,通常由控制器的操作方法决定)。 - Decent Dabbler
控制器负责将模型与正确的视图绑定。控制器负责事务,因此根据该事务的结果,选择适当的视图进行后续显示。这在实践中很困难,我曾经通过在视图中放置分支逻辑来破坏它,但应该避免这样做。 - dacracot
看看我的帖子,链接在这里... https://dev59.com/j0XRa4cB1Zd3GeqPq1IA ... 请记住,MVC是一种模式。 模式只是一种指导。 如果你是一个聪明的程序员,你会根据情况进行调整。 这没关系。 如果它是一个硬性规则,计算机就不需要你告诉它该怎么做了。 - dacracot

1

我认为这并不复杂。选a或者b。

控制器的工作是管理关系。它找到模型和视图,并提供视图所需的所有模型数据来完成其工作。就是这样。MVC并不规定数据的确切形式。

(a) 从简单开始。将模型对象直接传递给视图非常自然。如果您有一个关于Foo的页面,只需传递Foo即可。

(b) 但有时候--仅在某些情况下--您需要创建一个值对象/ DTO来将数据传递给视图(称为上面的ViewModel)。当视图与本地模型之间存在不匹配时,例如摘要视图。如果视图呈现了100万个对象的摘要,您不希望将模型对象交给视图;您希望将100万个对象的摘要交给视图。

而确切的实现方式取决于您使用的语言和框架。但我认为这些指南是一个很好的开始。


+1,虽然我要指出,在许多情况下,做b)(我称之为e),将您的DTO封装在一起是维护的福音。这是一个简单的重构,所以你可能只需要应用YAGNI并继续进行。此外,在ASP.NET MVC中,ViewModel不完全是DTO(ViewModel可以具有行为,DTO仅为getter/setter),但这已经非常挑剔了:P - Iain Galloway

1
对我来说,答案是e)。
如先前所述,理论上视图和模型之间应解耦。我更喜欢实现的方式是为模型编写一个视图助手(viewHelper),让视图可以使用API获取数据。这样,当模型发生改变时,视图不会受到影响;同时,视图也无需“获取”模型的内部信息,因为这些信息已由视图助手隐藏起来了。
例如:
class Post {
    public function export(ViewHelper $helper) {} // I try to avoid getters when I can
}

class PostViewHelper {
    public function getTitle($parameters) {} // title of current post in the loop
}

class PostView {
    private $helpers = array();
    public function loadTemplate($path) {}
    public function addHelper(ViewHelper $helper, $name) {}
    public function __get($key) {} // if exists $this->helper[$key] etc
}

在模板中

<h1><?php $this->post->getTitle(); ?></h1>

你可能想以不同的方式实现这个。但我的观点在于视图和模型是如何解耦的,引入了一个创建视图/模板API的中间视图助手。

0

嗯,b.

除了你将如何传递数据的一些技术细节之外,我并没有看出a和b之间的区别。

通常,您会将某些来自模型的数据与一个数据映射传递给视图。


这是一个SRP问题。重要的区别在于,如果你选择a)View和Model相互耦合,而如果你选择b)Controller和View相互耦合。如果你使用ViewModel,则ViewModel明确承担起在View和Model之间架起桥梁的责任,允许Model、View和Controller专注于各自的工作,而不用担心耦合 - 特别是如果你使用工厂模式创建ViewModel的话。 - Iain Galloway

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