概要
- 所有模式的使用都是视情况而定的,好处(如果有)始终在于降低复杂性。
- MVVM指导我们如何在GUI应用程序的类之间分配职责。
- ViewModel将模型中的数据投影为适合View的格式。
- 对于琐碎的项目,不需要MVVM。仅使用视图就足够了。
- 对于简单项目,可能不需要拆分ViewModel / Model,仅使用Model和View就足够了。
- Model和ViewModel不需要从一开始就存在,可以在需要时引入。
何时使用模式以及何时避免使用模式
对于足够简单的应用程序,每个设计模式都是多余的。假设您编写了一个GUI应用程序,该应用程序显示一个按钮,当按下该按钮时将显示“ Hello world”。在这种情况下,诸如MVC,MVP,MVVM之类的设计模式都会增加很多复杂性,而不会增加任何价值。
通常,仅仅因为某种程度上合适就引入设计模式总是一个错误的决定。应使用设计模式来降低复杂性,方法是直接降低整体复杂性,或者用熟悉的复杂性代替不熟悉的复杂性。如果设计模式无法通过这两种方式中的任何一种降低复杂性,请不要使用它。
为了解释熟悉和不熟悉的复杂性,请采用以下两个字符序列:
- “ D.€|Ré%dfà?c”
- “ CorrectHorseBatteryStaple”
虽然第二个字符序列的长度是第一个字符序列的两倍,但它比第一个字符序列更易于阅读,编写和记忆,所有这些都是因为它比较熟悉。对于代码中熟悉的模式也是如此。
当您认为熟悉程度取决于读者时,此问题会更上一层楼。一些读者会发现“ 3.14159265358979323846264338327950”比上述任何一个密码都更容易记住。有些不会。因此,如果您想使用一种MVVM,请尝试使用一种以您正在使用的特定语言和框架来镜像其最常见形式的镜像。
MVVM
就是说,让我们通过示例深入探讨MVVM的主题。MVVM指导我们如何在GUI应用程序中的类之间(或在各层之间-稍后再详细介绍)分配职责,目标是拥有少量的类,同时保持每个类的职责数量小且定义明确。
“适当的” MVVM至少假设一个中等复杂的应用程序,该应用程序处理从“某处”获取的数据。它可以从数据库,文件,Web服务或无数其他来源获取数据。
例
在我们的例子中,我们有2类View
和Model
,但没有ViewModel
。该Model
包装了一个CSV文件,它在启动时读取并保存在应用程序关闭时,所有的数据变化作出的用户。的View
是一个窗口类,显示从所述数据Model
表中的并让用户编辑的数据。csv内容可能看起来像这样:
ID, Name, Price
1, Stick, 5$
2, Big Box, 10$
3, Wheel, 20$
4, Bottle, 3$
新要求:以欧元显示价格
现在,我们被要求对我们的应用程序进行更改。数据由一个二维网格组成,该网格已经具有“价格”列,其中包含美元价格。我们需要添加一个新列,该列显示基于预定义汇率的欧元价格以及美元价格。csv文件的格式不得更改,因为其他应用程序可以使用同一文件,并且这些其他应用程序不受我们的控制。
一个可能的解决方案是将新列简单地添加到Model
类中。这不是最好的解决方案,因为Model
将所有公开的数据保存到CSV中-我们不希望在CSV中添加新的欧元价格列。因此,对的更改Model
将是不平凡的,而且描述Model类所做的事情也将变得更加困难,这就是代码的味道。
我们也可以在中进行更改View
,但是当前应用程序使用数据绑定直接显示Model
类提供的数据。由于我们的GUI框架不允许我们在表绑定到数据源的数据时在表中引入额外的计算列,因此我们需要对进行重大更改View
以使其工作,从而使工作View
更加复杂。
介绍ViewModel
ViewModel
该应用程序中没有任何内容,因为到目前为止Model
,它以Csv所需的确切方式呈现数据,这也是View
所需的方式。有一个ViewModel
无目的之间就已经增加了复杂性。但是,既然Model
不再以所需的方式显示数据View
,我们编写一个ViewModel
。该ViewModel
项目的数据Model
以这样的方式使得View
可以很简单。之前,View
班级已订阅该Model
班级。现在,新ViewModel
类订阅Model
该类,并将Model
的数据公开给View
-,并有一个额外的列以欧元显示价格。将View
不再知道Model
,它现在只知道ViewModel
,从View
外观上看与Model
以前相同-除了公开的数据包含一个新的只读列。
新要求:以不同方式格式化数据
客户的下一个要求是我们不应该将数据显示为表格中的行,而应以卡/盒的形式显示每个项目的信息(即行),并在屏幕上以4x5网格显示20个盒,显示20一次装箱。因为我们保持View
简单的逻辑,所以我们只View
用一个新的类来完全替换它,该类可以满足客户的需求。当然,还有另一个客户喜欢旧的客户View
,因此我们现在需要同时支持这两个客户。因为所有常见的业务逻辑都已经碰巧存在,所以ViewModel
这不是什么大问题。因此,我们可以通过将View类重命名为TableView
并编写一个新的CardView
以卡格式显示数据的类。我们还必须编写一些粘合代码,这可能是启动功能中的一个功能。
新要求:动态汇率
下一个客户要求是我们从互联网上获取汇率,而不是使用预定义的汇率。在这一点上,我们将重新讨论我之前有关“层”的声明。我们不会更改Model
班级以提供汇率。相反,我们编写(或找到)一个提供汇率的完全独立的附加类。这个新类成为模型层的一部分,我们ViewModel
合并了csv-Model和exchange-rate-Model的信息,然后将它们提供给View
。对于此更改,甚至不必触摸旧的Model类和View类。好吧,我们确实需要将Model类重命名为,CsvModel
然后将其称为new类ExchangeRateModel
。
如果我们当时没有引入ViewModel,而是等到现在才这样做,那么现在引入ViewModel的工作量会更多,因为我们需要从View
andModel
和move两者中删除大量功能。的功能ViewModel
。
单元测试后记
MVVM的主要目的不是将Model和ViewModel中的代码置于单元测试下。MVVM的主要目的是将代码分解为具有少量明确定义的职责的类。由包含少量明确定义职责的类组成的代码的几种好处之一是,将代码置于单元测试中更加容易。更大的好处是代码更易于理解,维护和修改。