为什么Qt滥用了模型/视图术语?

112

我认为Qt中使用的模型/视图控件术语存在缺陷。在他们的解释页上,他们表示将MVC简化为MV,通过合并View和Controller来实现,并提供了如下图片:

picture explaining Qt MVC

然而,我认为他们错误地命名了对象的角色,我认为:

  1. 他们称为合并控制器的视图实际上只是一个视图。
  2. 他们所谓的模型实际上只是一个控制器。
  3. 如果你真的想要一个模型,它应该在他们的“数据”所在的地方。

我说的是你在应用程序中通常和理智地使用Qt模型/视图组件的方式。以下是原因:

  1. 这通常是Qt组件,可以直接使用,无需添加特定于您的对象的控制器逻辑。
  2. 这几乎不是一个模型,只是因为你应该实现几个Qt方法,如rowCount、columnCount、data等,这些方法与你的模型无关。事实上,这些典型的模型方法可以在控制器中找到。当然,你可以同时实现控制器和模型逻辑,但首先这将是相当糟糕的代码设计,其次你会合并控制器和模型,而不是控制器和视图。
  3. 正如第二个原因所述,如果你想要分离模型逻辑,那么它肯定不是图片中的蓝色框,而是虚线框中的“数据”框(当然要与真实数据通信)。

Qt的术语有误,还是只有我不理解?(顺便说一句:这不是学术问题的原因是,我已经开始按照他们的命名编写我的项目,很快就发现代码明显不正确。直到那时我才意识到,我不应该尝试将模型逻辑放在他们所谓的模型中)


1
MFC使用CDoc和CView为2部分模型/视图GUI设定了标准,没有理由认为某个特定的MVC是“正确”的。 - Martin Beckett
@Martin B:我会看一下MFC,但即使有不同的MVC模型,我认为它们在术语上应该是一致的。我认为我已经提出了有效的论点,说明在这种特定情况下使用的术语不一致。他们只是简单地声明他们已经将View和Controller组合在一起,但我认为在这种情况下这是误导性的。我不认为有一个MVC模型,所有应用程序特定的逻辑,无论是表示还是模型逻辑,都必须放在一个称为Model的对象中。 - gorn
1
@Martin B:此外,在qt术语下,所有模型都有共同的api,这与模型结构无关,但与一般控制器结构有关,这清楚地表明将其称为模型是不正确的。我并不是说只有一个正确的MVC模型,但这并不意味着任何东西都可以被称为MVC模型。也许在MFC中它也存在缺陷,我可以看看它,但我更感兴趣的是Qt能否做到正确,而不是MFC,因为我不打算使用它。你有没有什么好的链接可以解释MFC的模型/视图分离? - gorn
1
MVC(Model-View-Controller)术语并不被普遍认同,因此您的问题可能被视为有争议的。然而,许多人都将同意Martin Fowler所做的出色工作(http://martinfowler.com/eaaDev/index.html)。通常,控制器处理用户输入,在这个意义上,Qt小部件绝对结合了视图和控制器。 - Arnold Spence
1
我知道MVC有很多种,但这并不意味着任何东西都可以成为MVC。Qt已经越过了界限,我给出了几个理由。Martin Fowler确实解释了不同类型的MVC,但它们中没有一个与Qt所宣称的MVC足够相似。最相似的是http://martinfowler.com/eaaDev/PresentationModel.html,但它将Presentation Model=Controller(用户交互)部分和Model(数据逻辑)区分开来。因此,尽管没有精确的MVC定义,但Qt并未遵循其中任何一种。如果您能给我提供这样的定义链接,请务必这样做。 - gorn
这篇文章仅提供链接,同时被转载到了程序员。提供一些小的奖励以吸引人们付出更多努力来回答这个问题会更好。 - Mark Booth
6个回答

93

简短回答

Qt的MVC只适用于一个数据结构。当谈论MVC应用程序时,您不应考虑QAbstractItemModelQListView

如果您想为整个程序使用MVC架构,则Qt没有如此“巨大”的模型/视图框架。但是对于程序中的每个数据列表/树,您都可以使用Qt MVC方法,该方法确实在其视图中具有控制器数据在模型内或外,这取决于您正在使用的模型类型(自己的模型子类:可能在模型中;例如QSqlTableModel:在模型外(但可能在模型中缓存))。要将模型和视图组合在一起,请使用自己的类,然后实现业务逻辑


详细回答

Qt的模型/视图方法和术语:

Qt为其模型提供简单的视图。它们内置了一个控制器:选择、编辑和移动项目是控制器在大多数情况下“控制”的内容。也就是说,解释用户输入(鼠标点击和移动)并向模型发出适当的命令。

Qt的模型确实是具有基础数据的模型。当然,抽象模型不持有数据,因为Qt不知道您想如何存储它们。但是,您通过将数据容器添加到子类并使模型接口访问您的数据来扩展QAbstractItemModel以满足您的需求。因此,事实上(我假设您不喜欢这个),问题在于需要编写模型,以便在数据结构中访问和修改数据。

在MVC术语中,模型包含数据和逻辑。在Qt中,您可以选择是否将一些业务逻辑包含在模型中或将其放在外部作为独立的“视图”。这里“逻辑”是什么意思都不清楚:选择、重命名和移动项目?=>已经实现。对它们进行计算?=>将其放在或放在模型子类中。从/向文件存储或加载数据?=>将其放在模型子类中。

我的个人观点:

给程序员提供一个好的且通用的MV(C)系统非常困难。因为在大多数情况下,模型都很简单(例如仅字符串列表),Qt还提供了一个现成的QStringListModel。但是,如果您的数据比字符串更复杂,则取决于您如何通过Qt模型/视图接口表示数据。例如,如果您有一个具有3个字段的结构体(例如具有名称、年龄和性别的人),则可以将3个字段分配到3个不同的列或3个不同的角色中。我不喜欢这两种方法。

我认为Qt的模型/视图框架只有在您想要显示简单数据结构时才有用。如果数据是自定义类型或者不是树形或列表形式组织的(例如图形),那么处理起来就会变得困难。在大多数情况下,列表已经足够了,甚至在某些情况下,模型应该仅包含一个条目。特别是如果您想要对不同属性(一个类的一个实例)建模一个单独的条目,那么Qt的模型/视图框架并不是从用户界面中分离逻辑的正确方法。

总而言之,我认为Qt的模型/视图框架只有在您的数据被Qt的观察者小部件之一查看时才有用。如果您要为仅持有一个条目的模型编写自己的查看器(例如应用程序的设置),或者如果您的数据不是可打印类型,则完全无用。


我如何在(较大的)应用程序中使用Qt模型/视图?

我曾经(与团队一起)编写了一个应用程序,该应用程序使用多个Qt模型来管理数据。我们决定创建一个名为DataRole的角色,以保存实际数据,每个不同的模型子类都具有不同的自定义类型。我们创建了一个外部模型类Model,它包含所有不同的Qt模型。我们还创建了一个外部视图类View,它包含与Model中的模型连接的窗口(小部件)。因此,这种方法是扩展的Qt MVC,适应于我们自己的需求。 ModelView类本身与Qt MVC无关。

我们把逻辑放在哪里?我们创建了一些类,通过从源模型中读取数据(当它们发生变化时)并将结果写入目标模型来对数据进行实际计算。从Qt的角度来看,这个逻辑类将是视图,因为它们“连接”到模型(不是用户的“视图”,而是应用程序的业务逻辑部分的“视图”)。

控制器在哪里?在原始的MVC术语中,控制器解释用户输入(鼠标和键盘),并向模型发出执行请求操作的命令。由于Qt视图已经解释了用户输入,如重命名和移动项目,因此不需要这个。但是我们需要的是一种超越Qt视图的用户交互解释。


3
@smerlin:我不认为那是正确的。无论是QListView还是QTreeView都只需要一个QAbstractItemView接口,这意味着一个自定义的子类或者一个具体的类如QStandardItemModel都能满足两者的要求。你可以使用一个模型来驱动树形视图和列表视图。 - jdi
1
@jdi:有些情况下,您的数据既是列表又是树形结构...例如,您可能希望将文件系统显示为树形结构,或者将所有文件显示为列表。Qts模型无法很好地支持这一点。一个支持树形视图的QAbstractItemModel实现只允许将根目录中的所有文件/目录显示为列表,但您无法将所有文件显示为列表。不要说将树形数据显示为列表没有用处。例如,如果您将文件显示为列表,则可以轻松对它们进行排序以查找具有最大文件大小的文件,而树形视图则无法实现此功能。 - smerlin
1
话虽如此,代理模型更多地是您视图的一部分(因为它修改了数据的“查看”方式),因此应该属于您的视图。如果您阅读了我的长篇答案:在“大”View类中,您应该添加代理模型,它具有树模型作为其基础模型,并由文件系统列表视图使用。正如您所说:不应该有相同数据的两个模型。绝对不行!(但代理模型不算作单独的模型。) - leemes
@leemes:我不同意原问题中提出的任何关于Qt的MV(C)概念与SmallTalk或其他重要地方定义的"MVC"不一致的论点。你能否调整你的回答,明确并早点表明你是否同意或不同意原问题中的三个争议点?在我看来,你对原问题中提出的所有观点都提供了有效的论据,但你也提出了许多其他观点,随着阅读的深入变得不太清楚。 - spinkus
1
@SamPinkus 这是因为这个问题没有明确的的答案。此外,QAbstractItemModel有不同的实现方式,其中一些是MVC模式下的模型,而另一些则不是。 - leemes
显示剩余4条评论

82
我同意你的观点,Qt的命名是有误导性的。但在我看来,这个问题不仅仅存在于Qt中,所有允许我们在实现用户界面时遵循关注点分离原则的框架都存在这个问题。当有人提出这样的框架,并找到了一个好的方法来保持“事物”分离时,他们总是感到有义务拥有称为“Model”的模块和其他称为“View”的模块。多年来,我一直在使用这些框架:
  • MFC
  • Qt
  • Swing
  • SWT
  • WPF with MVVM
如果你比较这些框架中“Model”和“View”这两个术语的使用方式,以及“View”、“Model”和(如果有)“Controller”的类所承担的责任,你会发现它们之间存在很大的差异。有一个比较不同概念和术语的对比将非常有用,这样从一个框架转换到另一个框架的人就有机会保持理智,但这需要大量的工作和研究。Martin Fowler的overview是一篇很好的阅读材料。
由于有许多不同的想法关于MVC模式可能看起来像什么,哪一个是正确的呢?在我看来,当我们想知道应该如何正确实现时,应该去问发明MVC的人。在original smalltalk paper中,它说:
视图负责管理分配给其应用程序的位图显示部分的图形和/或文本输出。控制器解释来自用户的鼠标和键盘输入,根据需要命令模型和/或视图进行更改。最后,模型管理应用程序域的行为和数据,响应有关其状态的信息请求(通常来自视图),并响应更改状态的指令(通常来自控制器)。
考虑到这一点,我将如下回答您的三个主要问题:
1.实际上,Qt组件“管理图形[...]输出”,并且“解释鼠标和键盘输入”,因此可以根据上述定义称之为合并的视图和控制器。
2.我同意您被迫合并控制器和模型(同样是根据上述定义)。
3.我再次同意。模型应仅管理“应用程序域”的数据。这就是他们所谓的“数据”。显然,处理行和列等通常与我们的应用程序域无关。

那么这对我们意味着什么呢?在我看来,最好弄清楚当使用“Model”和“View”这些术语时Qt实际上是什么意思,并在编写Qt程序时按照它们的方式使用这些术语。如果你一直被困扰,只会拖慢你的速度,而Qt中的设置确实允许优雅的设计-这比它们“错误”的命名约定更重要。


2
我认为代理是Qt的控制器,因为代理接收并将输入发送到模型,通过信号更新视图。 - ABu

13

术语并非对错,而是有用或无用。

你可能会稍微改变一下问题,问为什么Qt不更符合MVC的友好性。答案是早期的Qt开发人员认为,在GUI应用程序中将V与C解耦会导致Vs和Cs都变糟。 QWidget的设计试图使鼠标输入解释与像素输出决策紧密绑定,并且您可以看到这并不是通向MVC的道路。


我理解你的观点,基本上我会问为什么Qt不更加MVC友好,但是当Qt文档中使用的MVC术语与通常用于MVC的术语不同时,这很难做到(正如我在问题中所解释的那样)。当有广泛使用的术语并且有人将其用法与世界其他地方大不相同时,我倾向于认为它不仅无用,而且是错误和令人困惑的(这种困惑导致我首先提出了问题)。如果您有任何链接可以讨论或解释这些事情,我会非常感兴趣。谢谢。 - gorn
我无法对Qt文档现在所谈论的MVC方式做出任何评论。我早已离开Trolltech,对自己离开后文档中发生的一些事情感到困惑。(尽管在我的博客上,我有时会抱怨一些事情。) - arnt
你有没有了解Qt中MVC术语是如何达成一致的。它是在编写代码时使用的,还是仅在文档编写过程中使用的? - gorn
在 Trolltech 公司期间,我们的 Qt 文档中没有使用 “MVC” 这个词。总的来说,我认为最好记录下已有的内容,而不是写下不存在的内容。不过,在 gitorious 上,你可以查看是谁添加了这个文本,并直接联系那个人。 - arnt
1
一个不同的评论。在Qt的设计和早期实现阶段(当Trollech是一个三人公司时),我们确实讨论了MVC,并评估了一个使用MVC“正确”的GUI工具包,但我记不起它的名字了。我们的观点是,那个工具包非常难用,而MVC很大程度上是原因之一。 - arnt

3

作为 Model 函数对信息请求做出响应的功能,我认为定义 rowCountcolumnCount 等方法并没有问题。我认为 Model 是数据源的一种包装器(无论是 SQL 表还是数组),它以标准形式提供数据,并且您应该根据数据源结构来定义方法。


2

我认为他们的术语是正确的...尽管在实际应用中,根据你的抽象级别,很容易模糊模型、视图和控制器之间的界限:一个级别的视图可能是更高级别的模型。

我觉得混淆起源于他们的QAbstractModelItem类。这个类不是一个模型项,而是一个模型的接口。为了使他们的视图类与模型接口,他们必须创建一个通用的抽象模型接口。然而,一个模型可以是单个项目、项目列表、2个或多个维度的项目表等;所以他们的接口必须支持所有这些模型变化。不可否认,这使得模型项相当复杂,使其与实际模型配合工作的粘合代码似乎有点牵强。


尽管我同意您关于 QAbstractModelItem 类的观点,但我也认为即使没有这种复杂性,他们的 MVC 也是错误命名的。您能否解释一下为什么您认为他们的术语是正确的?我很想听听为什么我在我的三个论点中都不正确。 - gorn

0
我认为...他们所谓的模型实际上只是控制器。
不,他们的“模型”绝对不是控制器。
控制器是用户可见控件的一部分,用于修改模型(因此间接修改视图)。例如,“删除”按钮是控制器的一部分。
我认为经常会有混淆,因为许多人看到“控制器修改模型”之类的东西,就认为这意味着它们模型上的变异函数,比如“deleteRow()”方法。但在经典MVC中,控制器特指用户界面部分。改变模型的方法只是模型的一部分。
自从MVC被发明以来,它对控制器和视图的区分变得越来越紧张。想想一个文本框:它既显示一些文本,又让你编辑它,那么它是视图还是控制器?答案必须是它同时是两者的一部分。回到20世纪60年代你在电传打字机上工作时,区分更加清晰——想想ed——但这并不意味着当时用户的体验更好!

事实上,他们的QAbstractItemModel比通常的模型更高级。例如,其中的项可以具有背景颜色(技术上是刷子),这显然是视图属性!因此,有一种观点认为QAbstractItemModel更像是一个视图,而您的数据则是模型。事实是它处于传统意义上的视图和模型之间。但我看不出它是一个控制器;如果有什么,那就是使用它的QT小部件。


如果“模型”项目具有诸如背景颜色之类的可视化表示,则严格来说它不是“模型”。我认为这是OP的观点之一。 - Samantha Atkins

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