清洁架构:什么是视图模型?


13

鲍勃叔叔在他的《清洁架构》一书中说,主持人应该将收到的数据放入他称为“视图模型”的东西中。

在此处输入图片说明

这与Model-View-ViewModel(MVVM)设计模式中的“ ViewModel”是一样的吗,还是简单的数据传输对象(DTO)?

如果不是简单的DTO,它与View有何关系?视图是否通过观察者关系从视图获取更新?

我的猜测是它更像是MVVM中的ViewModel,因为罗伯特·马丁(Robert Martin)在他的书的第23章中说:

[演示者]的工作是接受来自应用程序的数据并对其进行格式化以进行演示,以便View可以将其简单地移动到屏幕上。例如,如果应用程序希望在字段中显示日期,它将向Presenter传递Date对象。然后,Presenter会将数据格式化为适当的字符串,并将其放置在称为View模型的简单数据结构中,View可以在其中找到它。

这意味着View以某种方式连接到ViewModel,而不是简单地将其作为函数参数接收(例如DTO就是这种情况)。

我认为这是因为,如果您查看图像,演示者将使用视图模型,而不是视图。演示者同时使用输出边界和输出数据DTO。

如果既不是DTO也不是MVVM中的ViewModel,请详细说明它是什么。


我认为答案是“取决于情况”。如果是Web应用程序,则视图模型基本上是DTO,因为它最终会序列化为HTML字符串。否则,视图模型只是用于向视图显示数据的专用对象。
格雷格·伯格哈特

在MVVM(WPF,应用程序的WinForms)ViewModel是包装的ControllerPresenterViewModel在鲍勃叔叔的干净架构。
法比奥

@Greg Burghardt-当ViewModel是专门的数据结构时,如何通知View更改?
Fearnbuster '18 -10-19

@Fabio-如果我正确地理解了您的意思,即您在MVVM模式中所说的话,ViewModel是否等于图最左侧组中的所有组件?如果这对Bob叔叔的体系结构是正确的,那么为什么他要分别列出Controller和Presenter?
Fearnbuster '18 -10-19

我认为他将输入和输出处理程序分离为不同的对象/类。在MVVM中,它可以是Controller-> ICommandPresenter-> data-binding mechanism
法比奥

Answers:


17

这与Model-View-ViewModel(MVVM)设计模式中的“ ViewModel”是否一样

不。

那是这样的

在此处输入图片说明

那有周期。鲍勃叔叔一直小心避免骑车

相反,你有这个:

在此处输入图片说明

当然没有周期。但是,这让您想知道视图如何知道更新。稍后我们将解决这个问题。

还是简单的数据传输对象(DTO)?

要引用前一页中的Bob:

如果愿意,可以使用基本结构或简单的数据传输对象。或者,您可以将其打包到一个哈希图中,或将其构造到一个对象中。

清洁建筑p207

所以,当然,如果您愿意。

但我强烈怀疑什么是真正窃听你的是这个

在此处输入图片说明

对UML的这种小小的滥用将源代码依赖的方向与控制流的方向进行了对比。在这里可以找到您问题的答案。

在使用关系中:

在此处输入图片说明 在此处输入图片说明

控制流的方向与源代码依赖项的方向相同。

在执行关系中:

在此处输入图片说明 在此处输入图片说明

控制流通常与源代码依赖项的方向相反。

这意味着您实际上正在查看此:

在此处输入图片说明

您应该能够看到控制流永远不会从Presenter传递到View。

这个怎么可能?这是什么意思?

这意味着视图要么具有自己的线程(这并不稀奇),要么(如@Euphoric所指出),控制流从此处未描述的其他内容进入视图。

如果是同一线程,则视图将知道何时可以读取视图模型。但是,如果是这种情况,并且视图是GUI,则当用户在等待数据库时将其移动时,将很难重新绘制屏幕。

如果视图具有自己的线程,则它具有自己的控制流。这意味着要实现此功能,视图将必须轮询视图模型以注意到更改。

由于演示者不知道该视图存在,并且该视图不知道该演示者存在,因此它们根本无法相互调用。他们不能互相殴打事件。可能发生的一切是Presenter将写入视图模型,而View将读取视图模型。每当有它的感觉。

根据此图,视图和演示者共享的唯一内容是对视图模型的了解。它只是一个数据结构。因此,不要指望它有任何行为。

这似乎是不可能的,但是即使视图模型很复杂也可以使它起作用。一个小的更新字段是视图必须轮询以检测更改的所有字段。

现在,您当然可以坚持使用观察者模式了,或者有一些框架性的东西可以使您看不到这个问题,但是请理解您不必这样做。

我在说明控制流程时有些有趣:

在此处输入图片说明

请注意,只要您发现流程与我之前定义的方向背道而驰,您所看到的就是调用返回。这个把戏不会帮助我们进入视图。好吧,除非我们先回到所谓的控制器。或者,您可以只更改设计,以便进入视图。这也解决了看起来像是数据访问及其接口的溜溜球问题的开始的问题

除了这里唯一要学习的另一件事是,用例交互器几乎可以按其想要的任何顺序调用事物,只要它最后调用演示者即可。


非常感谢您的回答,我已经看到您对有关Clean Architecture的其他各种问题的回答。您是否建议例如在View Model中不断查看View的标志,以查看是否有任何更改?然后,视图是否必须重新显示整个视图模型,还是应该使用一组嵌套标志来指示哪些数据已更改?
Fearnbuster '18 -10-19

该视图不必不断轮询。例如,web 1.0仅在用户点击重新加载时才进行轮询。不断轮询是一项设计决策,应考虑用户的实际需求。我只是说有可能。更新字段的目的是使检测更新快速。仅当视图模型复杂时才需要。还请考虑如果在演示者进行更新的一半时读取视图,会发生什么情况。
candied_orange

好的,非常感谢您的帮助。如果/当您遵循此体系结构时,这是您通常使用的技术吗?
Fearnbuster '18 -10-19

1
我认为这个答案将设计依赖性和运行时依赖性归为一个大错误。两者可以不同。
欣快的

1
@Euphoric为什么谢谢你。我将它们捆绑在一起,因为如果您对某些内容没有源代码依赖性,那么您将无法对任何内容使用运行时引用,因为您不了解它是什么。您所能做的就是像收藏一样保存引用。如果那是一个错误,我想理解。
candied_orange

2

我觉得这个问题太令人困惑了,因为我相信您对Martin的Clean Architecture和MVVM都误解了,所以需要花费大量文本和时间来正确解释问题。

首先要注意的是,您发布的图不完整。它仅显示“业务逻辑”,但缺少某种“编排器”,该“编排器”实际上使零件按正确的顺序移动。 在此处输入图片说明

Orchestrator的代码将非常简单

string Request(string request) // returns response
{
    Controller.Run(data);
    Presenter.Run();
    return View.Run();
}

我相信我在他关于清洁建筑的演讲中听到了马丁对此的谈论。

要指出的另一件事是candied_orange关于缺少循环的说法是错误的。是的,循环在代码的体系结构中不存在(也不应该)。但是运行时实例之间的循环很常见,并且通常可以简化设计。

在MVVM中就是这种情况。在MVVM中,View依赖于ViewModel,并且ViewModel使用事件将其更改通知给View。这意味着在类的设计中,从View类到Model类仅存在依赖关系,但是在运行时,View和ViewModel实例之间存在周期性依赖关系。因此,不需要Orchestrator,因为ViewModel将提供View方法来确定何时进行自我更新。这就是为什么此图中的“通知”使用“弯曲”线而不是直接线的原因。这意味着View会观察ViewModel中的更改,而不是说ViewModel依赖于View。

在此处输入图片说明

您应该从Martin的Clean Architecture中获得的最重要的信息不是设计本身,而是如何处理依赖关系。他在演讲中提出的关键点之一是,当存在边界时,所有跨越该边界的代码依赖项都将在单个方向上跨越边界。在图中,该边界由双线表示。并且有许多通过接口(和)进行的依赖反转InputBoundary,可以修复代码的依赖方向。OutputBoundaryDataAccessInterface

相反ViewModel,“干净架构”中的DTO只是普通的,没有逻辑。这通过<DS>标签变得明显。这就是为什么orchestrator有必要的原因,因为View不知道何时运行它的逻辑。

如果我要将图“展平”为运行时的样子,它将如下所示:

在此处输入图片说明

因此,在运行时,依赖关系处于“错误”方向,但这很好。

我建议观看他关于清洁建筑的演讲,以更好地理解他的推理。


您的“协调员”不应该呼叫演示者。用例交互器可以做到这一点。
candied_orange

@candied_orange是的,这是一个错误。
欣快的

感谢您的回答,得到一些不同的意见总是好事。我赞成你们两个的回答。你们两个是否都知道Robert Martin是否在某个地方实现了某种形式的体系结构的代码库?我看了看他的FitNess项目,但看不到树木茂密的森林。另外,我猜测的正确性是,即使我张贴的图像是鲍伯叔叔在演讲中始终使用的图表,但实际上仅是您的体系结构可能的示例?只要依赖关系流程正确,它看起来就可能大不相同?
Fearnbuster '18 -10-19

@Fearnbuster对于您的最后一个问题,是的。依赖的方向比结构更重要。我相信“干净架构”,“洋葱架构”和“六角架构”实际上是“保持检查依赖”相同思想的实现。
欣快的

@Euphoric老实说,我可能会说这种情况,因为在他的书的另一幅图像中(第8章的图8.2),他展示了一种看起来不同的体系结构。在该图中,Controller实际上是Interactor和Presenter之间的中间人。交互器也没有输出边界。似乎Interactor通过一个接口接收请求,然后通过相同的接口返回响应(我认为这是通过一个简单的函数返回值完成的,因为我想不出其他任何可以那样工作的机制)。
Fearnbuster '18 -10-19
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.