我们所开发的应用程序大多都需要提供一个图形用户界面(GUI). 关于GUI应用的架构设计, 已经有了很多模式, 比如Martin Fowler的blog中有一篇"GUI Architectures", 里面介绍了Form & Control, MVC, MVP, Passive View, Presentation Model, Supervising Controller, Event Aggregator, Observer Synchronization等多种模式. 模式可以帮助我们建立优雅的架构, 但前提是弄清楚模式的应用场景. 这些模式自然不是凭空产生的, 都是为了解决具体的问题. 模式在实现上的差别, 通常都体现了在约束间的不同取舍, 以及问题的差别. 弄清楚GUI应用面临的设计上的问题, 有助于我们正确的挑选设计方案. 下面我们来看一些GUI应用常见的设计问题.
第一个问题就是界面的变化和业务的变化频率不同, 通常是界面变化更频繁, 而我们希望一方的变化不至于影响另一方的逻辑. 对于这个问题, 一个自然的解决方案就是分离界面显示逻辑和后台业务逻辑. MVC和MVP都涉及到了这一点, 它们的共同特点就是把View和应用程序的其它部分分开了. 这是一个关键的分离, 从此之后应用被分为两部分, 抛开它们彼此可以独立的变化不说. 最大的好处是这两部分的问题也可以分而治之. 应用程序的其它部分有自己的问题和方案, 不在我们讨论范围内. 我们后面将聚焦在View和相关的显示逻辑方面的问题.
当然这种分离也不是没有代价的, 一个立即的问题就是View如何更新. MVC和MVP把View分出来制造了这个问题, 它们也同时提供了手段解决这个问题.
MVP中Presenter完成业务逻辑后可以拿到最新的Model, 它可以操控视图, 根据最新的Model来设置视图的各种属性并刷新.
MVC中Controller在完成业务逻辑操作后更新Model, Model变化时可以发出事件, View订阅Model更新事件来更新自己.
MVC有各种变体, 一种是Controller直接把Model推给View, View自己从Model中取出感兴趣的数据来刷新自己.
对视图更新的处理是MVC和MVP在实现上的主要区别: MVP中View不需要知道Model, Presenter直接操作View. MVC中View知道Model, 自己根据Model来更新自己的状态
(图片来自:http://msdn.microsoft.com/en-us/library/ff647859.aspx)
跟View相关的另一个常见问题就是可测试性. 即使其它分出去的部分可以独立测试, 但剩下来的View依然Hold住了一部分显示相关的逻辑. 显示逻辑也是逻辑, 也需要测试, 而通常直接测试GUI界面是相对难以测试的. 现有直接测试GUI的测试工具都面临以下问题:
测试耗时长, 因为要启动真实的应用
测试比较脆弱, 无论是可靠性还是可维护性, 因为界面元素的变化很频繁, 而通过编程来控制界面和用户真实操作经常有细微的差别, 尤其是时序相关的问题
一个思路就是把显示逻辑从View中分离, 让View退化为简单的GUI控件的容器. MVP做出了最初的努力, 而另外两个模式更加强调了这一点:Presentation Model和Passive View.
Passive View针对可测试性的方案是把所有的显示逻辑都从View中移除, View不再依赖任何Model, 只是提供接口完全被动的由Controller或者Presenter来设置显示所需数据并刷新
Presentation Model则封装了Domain Model拥有的数据到View显示所需数据之间的映射. View不再需要与Domain Model打交道自己来把业务数据转换成显示需要的数据, View只需从Presentation Model中取数据, 映射逻辑都在Presentation Model中. 而View所需数据和Presentation Model是简单的一一对应关系
我们上面讨论的都是相对简单的GUI, 比如我们其实假定了View和Model的一一对应, 甚至也假定了应用只有一个View. 然而我们还有多视图的情况. 多视图带来了以下问题:
当Model变化时如何保持多个视图间的一致性
多个视图间的交互的可控性
事件的循环触发问题
Martin Fowler blog中描述的Flow Synchronization和Observer Synchronization为当Model变化时刷新多个视图提供了两种方式,分别应对不同的情况.
Flow Synchronization 在Model变化后的某些明确定义的时机明确的更新所有受影响的View. 它的优点是显式, 直观, 可控, 缺点是很容易造成多个View之间彼此有依赖, 不易扩展, 因此它适用于视图较少的情况
Observer Synchronization则是让多个View都订阅Model的更新事件. 这是Observer模式在同步方面的应用, 具有Observer松耦合的特点. 缺点也不意外, 它让用户交互的影响变的隐式了, 不易于理解应用整体行为和开发时调试等.
传统上还有一种用于解决交互的可控性并让View之间彼此解耦的模式, 就是Mediator. 当我们在应用Flow Synchronization时, 如果把View之间的交互都抽取到一个中介者对象里面, 每个View都不知道其它View, 只知道中介者对象, 当有事件发生时, 由中介者对象来更新Model和其它View, 则我们可以获得相对清晰的交互和相对松散的耦合. 来看一下<<设计模式>>里面对Mediator的描述:
意图:
- 用一个中介对象来封装一系列对象交互.中介者使各对象不需要显式的相互引用,从而使其耦合松散,而且可以独立改变他们之间的交互
适用性:
- 一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解
- 一组对象引用其它很多对象并且直接与这些对象通信,导致难以复用该对象
效果
- 它将各Colleague解耦。有利于各Colleague间的松耦合。可以独立的改变和复用各Colleague类和Mediator类
- 简化了对象协议。用Mediator和各Colleague间的一对多的交互来代替多对多的交互
- 对对象如何协作进行了抽象?将中介作为一个独立的概念并将其封装在一个对象中,有助于弄清一个系统中的对象是如何交互的
- 控制集中化. 中介者模式将交互的复杂性变为中介者的复杂性
多视图的另一个问题就是事件的循环触发问题. 场景如下: 事件A发生->事件A处理函数->处理过程中触发了事件B->事件B处理函数->处理过程中又触发了事件A->...一个简单的例子比如界面上有两个文本框, 要保证它们的和一直都是100. 比如文本框A输入30的时候, 文本框B要显示70. 文本框B输入40的时候, 文本框A要显示60. 我们在处理第一个输入事件的时候需要设置第二个文本框的值, 而这个设值动作会触发第二个文本框的事件处理, 它也要设置第一个文本框的值...如此循环.
通常的处理方式有几种, 目的都相同: 尽量减少不必要的事件发送
状态真正改变时才发事件, 状态没有改变的话就不发事件. 上面例子中的TextBox控件. 如果连续用相同的参数调用其 SetText, 除了第一个调用可能会触发TextChanged事件外, 后续的操作都不会触发, 因其Text并未真的改变. 在我们的领域模型中触发事件可以遵循相同的Pattern
避免重入. 当事件处理函数开始事件处理的时候, 把自己置成一个不同的状态, 比如"处理中", 事件处理结束的时候再置回正常状态. 当在事件处理过程中触发新的事件又导致事件处理函数被调用, 可以检查自己是否在"处理中"的状态, 如果是的话忽略即可.
根据事件的源头来决定是否处理. 这需要在事件的上下文中加入额外信息, 比如事件的发送者sender. 微软的CAB框架允许指定事件的Scope, 这样处理函数可以只处理自己感兴趣范围内的事件.
严格遵循CQRS原则, 更新Model的函数和刷新视图的函数应该是两个函数, 分别是对用户输入事件的响应和Model改变事件的响应. 这样刷新视图不会再引入新的事件, 减少循环的几率.
使用细粒度的事件. 粒度过粗会引发不必要的响应, 增加循环的可能
谈到事件的粒度, 过细的粒度会引起另外一个问题: 注册事件处理函数太繁琐, 不易看清交互.Event Aggregator可以来解决这个问题
模式
最后回过头来看一下已有的几个模式各自的重点
其中:
MVP比MVC更强调显示逻辑跟视图的分离
MVP, Presentation Model和Passive View都强调视图跟显示逻辑的分离, 程度不同: MVP引入这一分离, Passive View分离的最彻底最可测, Presentation Model介于两者之间.
Presentation Model比MVP和Passive View更强调的是为显示逻辑创建单独的Model, 而不是依赖于Domain Model.
更全面的比较, 请参见老马的"GUI Architectures", 及里面的链接
分享到:
相关推荐
通过采用**TASKMASTER**模式,开发者能够构建出更加灵活、易于维护和扩展的GUI应用程序。这种模式特别适用于那些需求变化频繁、用户交互复杂的GUI应用,可以帮助开发团队更高效地应对项目挑战。
总之,Python GUI文件夹管理系统是一个结合了Tkinter、文件操作和事件驱动编程的综合项目。通过此项目,开发者不仅可以学习到如何创建GUI应用,还能加深对文件系统操作的理解。实践中,要注意用户体验、错误处理和...
在 GUI 应用程序中,需要使用特定的语言和开发工具来创建和设计用户界面。常见的 GUI 应用程序开发语言包括 Java、C++、Python 等,而开发工具包括 Eclipse、Visual Studio、Qt Creator 等。 本文档将介绍如何使用 ...
《Win32::GUI::Skin——打造个性化的Win32应用程序》 Win32::GUI::Skin是一个在Perl编程环境...如果你是一名Perl程序员,并希望让你的Win32应用程序在视觉上脱颖而出,那么Win32::GUI::Skin绝对值得你深入研究和使用。
在Matlab中,图形用户...文本框是GUI中最基本的组件之一,它允许用户输入和显示文本。本文将详细介绍如何在Matlab界面中添加文本框,包括文本框的基本属性设置、事件处理以及如何在App Designer和GUIDE中使用文本框。
2. 初始化GUI:调用`GUI_Init()`函数初始化EMWIN库,为后续控件创建和事件处理做好准备。 3. 创建BUTTON控件:使用`GUI_CreateButton()`函数创建BUTTON,参数包括按钮ID、位置、大小、文本内容、回调函数等。例如:...
创建一个GUI应用程序,用于转换若干种货币。提供给用户一个JTextArea以用于输入数量。同时还应提供给用户一种选择源货币名称和目的货币名称的途径。同时为自己的测试设置至少三种不同的货币名称及转换汇率。
表格组件是Matlab GUI开发中用于显示和编辑表格数据的重要组件。无论是使用App Designer还是GUIDE,你都可以轻松...随着你对Matlab GUI开发的深入,你将能够利用表格组件的强大功能来创建更加复杂和专业的GUI应用程序。
pyside本文将为您介绍PySide GUI应用程序的开发,涵盖知识领域、技术关键词、内容关键词和用途。本文适用于对PySide GUI开发感兴趣的读者,希望您能在阅读完本文后,对PySide GUI应用程序的开发有一个全面的了解。 ...
Java提供了多种图形...理解AWT和Swing的区别对于选择合适的GUI工具集至关重要,尤其是在需要构建跨平台且具有一致外观和感觉的应用程序时。随着Java的不断发展,Swing仍然是创建复杂和交互式GUI应用程序的有力工具。
颜色选择器是Matlab GUI开发中常用的组件之一,它允许用户通过弹出式颜色选择对话框来选择颜色。...随着你对Matlab GUI开发的深入,你将能够利用颜色选择器的强大功能来创建更加复杂和专业的GUI应用程序。
matlab gui应用实例 matlab gui应用实例 matlab gui应用实例 matlab gui应用实例 matlab gui应用实例 matlab gui应用实例 matlab gui应用实例
3. **设计模式与最佳实践**:作者可能会分享在GUI应用开发中的常见设计模式,如模型-视图-控制器(MVC)模式,以及如何在Go中实现这些模式以提高代码的可读性和可维护性。 4. **并发编程**:Go语言的一大特色是其...
GUI 应用 基础 pdf GUI 应用 基础 pdf GUI 应用 基础 pdfGUI 应用 基础 pdf GUI 应用 基础 pdf
通过理解和应用这些模式,开发者能够更好地设计和实现嵌入式GUI系统,提高代码的可维护性和可扩展性。例如,通过观察者模式,可以轻松地实现数据驱动的界面更新;通过工厂模式,可以根据需求动态创建和销毁GUI组件,...
### 开源GUI库 uGui 的实现原理与应用分析 #### 概述 uGui是一款开源且免费的小型图形用户界面(GUI)库,以其简洁、清晰的代码风格著称。适用于资源有限的嵌入式系统中,特别是对于那些需要简单图形用户界面的应用...
本章主要围绕GUI案例设计应用展开,具体包括数据概率密度函数统计软件设计、曲线线型及颜色更改软件设计、数据拟合GUI设计、倒立摆系统GUI设计、图像颜色空间转换GUI设计、图像灰度处理GUI设计、图像滤波去噪GUI设计...
以上内容详细介绍了如何使用Tkinter来创建基本的GUI应用程序,包括窗口的基本配置、常见控件的使用以及布局管理器的应用。希望这些信息能够帮助您更好地理解Tkinter的基础知识,并在实际项目中灵活运用。
深度学习在GUI测试中的应用可以提高测试效率和代码覆盖率,减少测试时间和成本,并提高软件的质量和可靠性。该技术已经被广泛应用于移动应用、Web应用和桌面应用的测试中,并将继续推动软件测试技术的发展。
例如,以下代码可以创建一个包含两个选项卡的GUI: ```matlab fig = uifigure('Name', '选项卡示例'); tabGroup = uitabgroup(fig); tab1 = uitab(tabGroup, 'Title', '选项卡1'); % 在这里添加选项卡1的内容 tab...