交互式软件架构:核心组件与实现策略解析
在当今数字化的时代,交互式软件无处不在,从我们日常使用的手机应用到复杂的工业控制系统,都离不开交互式软件的支持。那么,交互式软件是如何构建的呢?其背后的架构又包含哪些关键要素?本文将深入探讨交互式软件架构的核心组件,包括模型 - 视图 - 控制器(MVC)范式、窗口系统、视图呈现、控制器问题以及模型实现等方面,为你揭示交互式软件背后的奥秘。
1. 交互式软件的整体架构
交互式软件的基本形式可以看作是一个接收来自交互设备的输入流,解释这些输入,然后在工作站显示屏上绘制图像的过程。然而,在实际应用中,由于可能同时有多个应用程序处于活动状态,现代工作站需要通过窗口系统来对交互设备和显示屏空间进行多路复用。
窗口系统允许应用程序分配矩形(在某些系统中还可以是任意形状)的窗口,每个应用程序可以将窗口视为独立的显示屏。当用户操作交互设备时,会生成输入事件,窗口系统负责确定哪个窗口应该接收每个事件,并将其转发给拥有该窗口的应用程序进行处理。
在交互式应用程序中,每个窗口由三个主要组件管理:模型(Model)、视图(View)和控制器(Controller),即MVC范式。这一范式由施乐公司的SmallTalk小组在开发面向对象的交互式环境时提出。
- 模型(Model) :存储和管理对应用程序功能至关重要的信息。例如,在一个电路图布局应用中,模型会管理表示电路图的数据结构。
- 视图(View) :负责在屏幕上直观地呈现模型中的数据。在上述例子中,视图负责在屏幕上绘制电路布局和零件列表。
- 控制器(Controller) :通过窗口系统接收来自交互设备的输入事件流,并解释这些事件以确定用户的意图。
这三个组件之间需要相互通信以实现整个交互过程。控制器需要调用模型中的命令来更新信息或执行操作,也可以请求视图以某种方式修改显示,如滚动窗口或关闭窗口。视图需要从模型中请求数据以正确绘制显示内容,而模型在数据修改时需要通知视图更新显示。
此外,许多交互式应用程序会使用预编程的控件或小部件(Widgets),这些小部件可以看作是小型的MVC系统。例如,滚动条有自己的视图区域、模型(由最大值、最小值和当前值三个整数组成)和控制器。当应用程序更改模型值或用户拖动滚动条时,视图会相应地更新显示。
在现代交互式系统中,应用程序的控制流通常遵循一个主循环,该循环不断从窗口系统的事件队列中检索输入事件,根据事件指向的窗口选择相应的控制器,并通知其处理事件。控制器在处理事件时会在适当的时候调用模型、视图或生成更高级别的事件,这些事件会被放入事件队列并传递给系统的其他部分。
2. 窗口系统的关键功能
窗口系统的主要目的是为应用程序营造每个窗口都是独立显示屏且有自己输入事件流的错觉。为了实现这一目标,窗口系统需要解决以下四个主要问题:
2.1 管理窗口结构
窗口系统管理窗口的方式主要有两种:扁平结构和树形结构。苹果Macintosh采用扁平结构,只管理主窗口,其他区域由小部件或应用程序在窗口内管理。而大多数窗口系统采用窗口层次结构,每个子窗口可以有自己的窗口并管理自己的事件。不同的窗口系统在子窗口与父窗口的几何关系上有所不同,例如,有些系统中子窗口必须完全位于父窗口分配的空间内并随父窗口移动,有些系统允许子窗口位于父窗口空间之外但仍随父窗口移动,还有些系统中子窗口的位置可以完全独立于父窗口。
2.2 分配屏幕空间
屏幕空间分配给窗口是窗口系统的职责,而非单个应用程序。大多数窗口系统允许应用程序请求窗口的特定位置和大小,但窗口系统并不一定满足这些请求。常见的窗口组织方式是重叠窗口模型,用户可以控制窗口的放置和大小。窗口管理器负责解释用户事件以执行窗口大小调整和放置操作,这些操作通常在应用程序窗口的框架中执行,该框架有自己的MVC结构。当窗口被移动或调整大小时,窗口管理器会生成更高级别的事件通知应用程序的窗口控制器。
2.3 事件调度
窗口系统在收到输入事件时,需要决定哪个窗口应该接收该事件。在调度事件时,我们区分几何事件和非几何事件。几何事件自然地与屏幕上的位置相关联,如鼠标移动事件;非几何事件,如键盘事件,则没有自然的位置关联。
对于几何事件,窗口系统通常会确定几何上包含事件位置的最前面的窗口。在层次化窗口系统中,可能会存在歧义,不同的系统采用不同的处理方式,如X系统采用自下而上的事件处理方式,即最低层的窗口先接收事件,如果该窗口拒绝处理,则将事件向上传递;而MS - Windows采用自上而下的事件处理方式,即最高层的窗口先接收事件,该窗口可以选择将事件转发给其他窗口。
对于非几何事件,窗口系统有两种常见的处理方式:鼠标位置和键盘焦点。鼠标位置方式根据鼠标的当前位置来调度非位置事件,但这种方式存在一些问题,如鼠标意外移动可能导致键盘事件被重定向到不同的窗口,以及在填写表单时无法满足“Tab”功能的需求。而键盘焦点策略则是当窗口或小部件被鼠标或“Tab”处理选中时,请求键盘焦点,键盘事件总是发送到具有键盘焦点的窗口或小部件。
此外,有些窗口或小部件可能需要处理发生在其自身边界之外的交互,例如Macintosh Finder在文件拖动过程中,即使文件不在任何关联窗口内,也需要处理输入事件。这种情况下,窗口或小部件可以通过请求鼠标焦点来解决,鼠标焦点会覆盖正常的输入空间多路复用,将所有输入事件都导向具有焦点的窗口或小部件。
2.4 管理绘图活动
在窗口系统中,交互式应用程序希望将窗口视为完全属于自己的。但当窗口相互重叠时,一个窗口的绘图操作可能会影响到其他窗口。窗口系统通过管理每个窗口的可见区域来解决这个问题,任何绘制到窗口的操作都会被裁剪到该区域,以防止损坏其他窗口。
当窗口移动或窗口被置于最前面时,之前被遮挡的部分可能会变得可见,窗口系统会检测这些情况并创建更高级别的事件,通知应用程序哪些窗口显示区域需要更新。
3. 视图呈现的核心要点
视图组件的主要目的是处理MVC单元中的所有几何和绘图问题,具体包括三个方面:
3.1 绘图
现代2D窗口图形的一个重要特征是将对象的所有绘图操作集中在一个例程或模块中。在大多数面向对象的系统中,每个视图对象都有一个Redraw方法,负责在屏幕上绘制对象。该方法通常在响应窗口系统生成的Redraw事件时被调用,这些事件可能是由于窗口移动、更改或应用程序自身触发的。
在绘图过程中,通常不需要重绘整个视图,而只需要重绘部分区域,如窗口中被暴露的部分或因移动而需要重绘的单个电路部件。窗口系统会维护一个更新区域,该区域既是传递给Redraw方法的参数,也是裁剪区域。通过裁剪区域,简单的Redraw方法可以绘制整个视图,由窗口系统的图形包去除不需要的绘图片段。更高效的方法是将更新区域与模型/视图的几何形状进行比较,以确定哪些片段需要处理,从而提高绘图速度。
3.2 更新绘图
模型中的信息可能会因为各种原因发生变化,当这种情况发生时,模型会向所有注册的视图发送事件或面向对象的消息。每个视图在收到通知后,需要更新其在屏幕上的显示部分。
简单的擦除/绘制策略无法处理对象之间可能的重叠问题,特别是在存在其他未知窗口的情况下。因此,所有2D窗口系统都采用损坏/重绘(damage/redraw)架构来更新屏幕。视图在检测到模型变化时,会标记需要更新的区域,窗口系统收集这些损坏区域并生成相应的重绘事件,以恢复损坏的区域。
例如,当移动一个白色多边形时,视图会标记覆盖多边形新旧位置的矩形区域为损坏区域,窗口系统会生成重绘事件,视图会在旧区域绘制背景或其他对象,在新区域绘制白色多边形,从而正确显示模型的新状态。
在某些情况下,如需要连续移动对象时,如果损坏/重绘机制的更新速度不够快(小于1/5秒),则需要使用专门的优化技术。
3.3 选择几何
视图的最后一项任务是处理来自控制器的选择请求。只有视图拥有实际的几何信息,能够确定给定鼠标输入点是否选择了模型中的对象。这本质上是一个确定点是否接近或位于特定几何形状内的问题,可以参考解析几何相关的书籍来解决。
3.4 布局设计工具
视图决定了用户界面的视觉呈现。在某些情况下,如芯片布局视图,通常由程序员手动编码实现;而在其他情况下,如芯片布局左侧的按钮、菜单、调色板和滚动条等,可以使用可视化布局工具。许多商业工具包提供了带有小部件调色板的布局工具,设计师可以从调色板中选择工具并在2D表面上进行布局,该过程通常会为每个小部件分配一个矩形区域并存储。
然而,当窗口可调整大小时,固定的矩形区域无法满足需求。例如,窗口调整大小时,标签希望保持在中心,按钮希望保持附着在侧面,芯片布局区域希望填充整个窗口。这就需要更通用的机制来处理视觉布局。
3.5 约束系统
约束系统是处理视觉布局的一种更通用的机制。约束是一个关联两个或多个值的方程,例如ScrollBar.Bottom = Window.Bottom ,Button.Left = (Window.Left + Window.Right)/2 。
约束系统可以表达各种几何关系,当窗口大小改变时,会重新评估约束以确定所有对象的新位置。但约束系统的一个核心问题是找到高效的算法来求解约束系统,通常需要通过某种方式限制约束集。
为了让界面设计师更方便地使用约束系统,一些图形用户界面被开发出来,隐藏了数学复杂性。例如,Cardelli的图形附件模型和Hudson的Apogee系统。此外,还有一种“盒子和胶水”模型,它采用自下而上的方式,每个对象向其父窗口报告其所需的大小和伸缩性,父窗口根据这些信息更智能地划分可用空间并向下传播位置约束信息。
3.6 模型/视图映射工具
为了帮助开发视觉显示与模型信息之间的映射,人们开发了一些工具。例如,Browse / Edit模型将视图/控制器单元视为语义对象(模型)的编辑器,将界面设计问题转化为将编辑器的语义与模型绑定。
在将模型中的值与视觉对象的几何位置相关联时,约束系统是合适的机制,但约束的规范是一个问题。一些系统,如GITS系统、Peridot系统和Lemming系统,提供了不同的解决方案。GITS系统允许用户以图形方式指定视图几何与模型之间的约束;Peridot系统通过演示来开发约束,系统猜测合适的约束并请求用户确认;Lemming系统使用多个示例推断几何与模型之间的任意映射。
以下是一个简单的mermaid流程图,展示了视图呈现的主要流程:
graph TD;
A[开始] --> B[绘图请求];
B --> C{是否有更新区域};
C -- 是 --> D[确定需要重绘的部分];
C -- 否 --> E[绘制整个视图];
D --> F[绘制相关部分];
E --> F;
F --> G{模型是否更新};
G -- 是 --> H[标记损坏区域];
G -- 否 --> I[结束];
H --> J[窗口系统生成重绘事件];
J --> K[视图更新显示];
K --> I;
4. 控制器面临的挑战与解决方案
控制器的主要作用是处理输入事件并以有意义的方式进行处理。然而,输入设备生成的事件数量远少于可以采取的操作数量,因此控制器的一个主要任务是对输入事件的含义进行多路复用,主要有两种方法:空间多路复用和时间多路复用。
4.1 空间多路复用输入事件
空间多路复用根据输入事件在屏幕上的发生位置赋予其不同的含义。例如,在芯片布局窗口中,鼠标点击删除按钮和点击电线的含义是不同的。但对于没有关联位置的事件,如键盘事件,空间多路复用无法直接应用。
窗口系统处理非位置事件有两种常见的策略:鼠标位置和键盘焦点。鼠标位置策略根据鼠标的当前位置调度非位置事件,但存在鼠标意外移动导致事件重定向和无法满足表单“Tab”功能的问题。键盘焦点策略则是当窗口或小部件被选中时请求键盘焦点,键盘事件总是发送到具有焦点的窗口或小部件。
此外,一些窗口或小部件可能需要处理发生在其自身边界之外的交互,通过请求鼠标焦点可以解决这个问题,鼠标焦点会覆盖正常的输入空间多路复用,将所有输入事件导向具有焦点的窗口或小部件。
4.2 顺序调度输入事件
即使使用了空间多路复用,仍然可能无法从输入事件流中提取足够的含义。例如,拖动矩形的操作涉及鼠标按下、零个或多个鼠标移动和鼠标释放,每个事件都有特定的操作,且这些操作的含义还会受到鼠标按下位置和鼠标是否移出窗口等因素的影响。
为了处理输入事件的顺序问题,人们提出了几种模型。早期的模型使用有限状态机或语法来表示事件顺序,但这些模型适用于高度有序的输入流,对于用户输入通常不太有序的情况,状态机方法需要指数级数量的状态和转换来建模。
基于产生式系统的规范被提出,用于对输入流的顺序方面进行建模,并为程序员开发此类系统提供帮助。类似的方法还包括基于Petri网和时态逻辑的模型。
使用输入序列的规范的一个主要优点是可以自动分析以确保某些属性成立,特别是控制器能够正确响应意外事件。错误处理在手写控制器中是一个特别的问题,而这些更正式的表示方法可以自动处理错误输入而不会崩溃。
4.3 面向对象的实现
目前,实现控制器最流行的模型是将控制器和视图组合成一个与某个窗口区域相关联的单个对象类。这个视图/控制器类为可以接收的各种事件定义了方法,包括重绘事件、输入事件、焦点协商事件和窗口大小调整事件。这些方法通常在抽象类中定义,程序员可以创建子类并覆盖适当的方法来提供具体实现。
然而,这种方法存在两个主要问题:一是事件的顺序多路复用完全由程序员负责;二是视图/控制器对象与它们所呈现和操作的模型之间的连接问题。在小部件设计中,希望编写通用的小部件,以便可以以各种方式连接到不同的模型。
5. 模型的设计与实现
模型的实现定义了交互式应用程序的具体功能。一般来说,模型的设计由要实现的目标的任务分析驱动,从分析中设计出一组具有数据和方法的对象,以满足用户的功能需求。
在芯片布局示例中,模型可能包括一个Circuit类,其中包含Chips和Wires类。每个类都有关于其内容的信息以及创建、修改和删除对象的方法。控制器通过调用这些方法来更改模型,而视图需要这些方法来获取模型信息以支持重绘功能。
除了满足应用程序的功能需求外,模型的实现还需要解决以下与用户界面相关的问题:
5.1 视图通知
在MVC架构中,当控制器或应用程序代码修改模型中的信息时,所有相关的视图都必须得到通知,以便更新显示以反映变化。例如,在芯片布局示例中,同一模型有布局和零件列表两种不同的视图。
为了实现视图通知,需要一种机制让视图可以向模型注册,而模型可以通知所有视图其变化。一种简单的实现方式是定义一个抽象的Model类,包含RegisterView、UnregisterView和NotifyViews方法,以及一个抽象的View类,包含RegisterModel方法和虚拟方法ModelChangeNotify。
然而,一个问题是模型如何准确地通知视图哪些部分发生了变化。如果NotifyViews方法没有参数,视图将不知道模型的哪部分发生了变化,可能需要损坏整个布局显示以确保正确绘制。因此,需要一种通用的方式来向视图传达模型的具体变化。
5.2 视图/控制器接口
设计接口的一个关键问题是将用于实现视图和控制器的通用用户界面工具与各种应用程序模型连接起来。早期的系统,如Motif,允许设计师将C程序的地址附加到视图/控制器中应用程序模型可能感兴趣的位置,但这种方式比较笨拙。
一些系统提出了自动从组成模型的类的描述中生成接口的方法,如Mickey和Sushi系统。微软的Visual C++使用其Class Wizard在界面布局工具中实现这些连接。
5.3 复制、粘贴和剪切
现代用户界面的一个关键组件是能够在应用程序的一个部分复制或剪切信息,并将其粘贴到同一应用程序或不同应用程序的不同部分。一些窗口系统在处理文本和有时像素映射信息时可以有限地实现这一功能,但一般来说,问题更为复杂。
所有现代窗口系统都支持粘贴缓冲区的概念,在处理粘贴缓冲区时,需要解决两个主要问题:一是信息在不同应用程序之间传递时可能有多种格式;二是信息可能非常大,不应不必要地复制。
当进行复制操作时,模型需要向窗口系统注册应该放入粘贴缓冲区的信息的指针,并注册其可以生成的各种信息格式的标识符。当进行粘贴操作时,目标模型需要选择一种格式并请求信息传输,源模型生成请求格式的信息,目标模型将其转换为自身数据的修改并通知其视图更新显示。
5.4 撤销和重做
直接操作系统的一个关键特性是为新用户提供安全的探索环境,让他们可以尝试各种操作而不用担心永久丢失信息。撤销机制可以将模型的状态恢复到操作之前的状态,而重做功能则可以恢复刚刚撤销的操作。
撤销有不同的级别,从简单的撤销最后一次操作到维护最后N个操作的历史记录,甚至可以选择性地撤销其中任何一个操作。无论采用哪种方式,都需要保存足够的信息以便将模型恢复到之前的状态。
主流的撤销策略使用一组命令对象作为控制器/模型的接口。定义一个抽象的Command类,包含Do和Undo方法,每个可以由控制器在模型上执行的操作都由一个单独的Command子类表示。当执行操作时,会创建一个新的命令对象,调用其Do方法,该方法会先保存恢复模型状态所需的信息,然后修改模型并将自身放入撤销历史记录中。当用户请求撤销时,从历史记录中选择一个命令对象并调用其Undo方法,将模型恢复到原始状态。
然而,这种撤销架构的一个问题是需要定义大量的Command子类,这会使新交互式模型的设计和实现变得非常繁琐。
6. 总结
用户界面软件可以从概念上划分为模型、视图和控制器(MVC)三个部分。在许多情况下,这些结构由更小的MVC单元组成,用于与界面的较小片段进行交互。
模型实现了应用程序的功能语义,负责撤销/重做操作以及在数据更改时通知所有视图。视图负责所有显示和几何问题,控制器则解释用户输入流,为输入赋予含义并调用模型中的命令以实现用户的需求。所有这些活动都通过窗口系统进行管理,窗口系统负责管理交互资源,如输入设备和屏幕空间。
通过深入理解交互式软件架构的各个组件和实现策略,我们可以更好地设计和开发出高效、易用的交互式软件,为用户提供更好的体验。
希望本文能为你在交互式软件架构的学习和实践中提供有价值的参考,如果你对某个部分有更深入的疑问或想要进一步探讨相关话题,欢迎在评论区留言。
交互式软件架构:核心组件与实现策略解析
7. 交互式软件架构的应用案例分析
为了更直观地理解交互式软件架构的实际应用,下面通过几个具体的案例进行分析。
7.1 芯片布局应用
在芯片布局应用中,我们可以看到MVC架构的典型应用。模型部分包含了表示电路、芯片和电线的数据结构和相关方法。视图负责将这些信息以可视化的方式呈现给用户,如绘制芯片布局和零件列表。控制器则处理用户的输入事件,例如鼠标点击选择芯片或拖动电线等操作。
当用户在芯片布局窗口中移动一个芯片时,控制器接收到鼠标拖动事件,调用模型中相应的方法来更新芯片的位置。模型更新后,通知所有注册的视图进行更新。视图根据模型的变化,标记需要更新的区域,窗口系统收集这些损坏区域并生成重绘事件,最终视图在新的位置绘制芯片,完成界面的更新。
在这个过程中,窗口系统起到了重要的作用。它管理窗口的结构和屏幕空间的分配,确保不同的窗口能够合理地显示在屏幕上。同时,窗口系统还负责事件的调度,将用户的输入事件准确地传递给相应的控制器。
7.2 图形编辑应用
图形编辑应用也是交互式软件的典型代表。以一个简单的绘图工具为例,模型可以存储图形对象的属性,如形状、颜色、位置等。视图负责将这些图形对象绘制在屏幕上,提供用户直观的界面。控制器处理用户的绘图操作,如绘制线条、矩形、圆形等。
在图形编辑过程中,用户可能会进行复制、粘贴和撤销等操作。复制操作时,模型将相关图形对象的信息注册到窗口系统的粘贴缓冲区,并提供多种格式的标识符。粘贴时,目标模型选择合适的格式并请求信息传输,完成图形的复制。撤销操作则通过命令对象来实现,每个绘图操作都对应一个命令对象,用户请求撤销时,调用相应命令对象的Undo方法,将模型恢复到操作之前的状态。
以下是一个简单的表格,对比芯片布局应用和图形编辑应用在MVC架构中的不同组件的特点:
| 应用类型 | 模型 | 视图 | 控制器 |
| ---- | ---- | ---- | ---- |
| 芯片布局应用 | 包含电路、芯片和电线的数据结构和方法 | 绘制芯片布局和零件列表 | 处理鼠标点击、拖动等操作 |
| 图形编辑应用 | 存储图形对象的属性 | 绘制各种图形 | 处理绘图、复制、粘贴、撤销等操作 |
8. 交互式软件架构的优化策略
为了提高交互式软件的性能和用户体验,我们可以采取以下优化策略:
8.1 视图更新优化
在视图更新方面,采用更高效的算法来确定需要更新的区域。例如,通过比较模型的新旧状态,只标记发生变化的部分为损坏区域,减少不必要的重绘操作。同时,可以使用双缓冲技术,先在后台缓冲区进行绘制,然后一次性将缓冲区的内容复制到屏幕上,避免闪烁现象,提高绘图的流畅性。
8.2 控制器事件处理优化
对于控制器的事件处理,可以采用事件队列和多线程技术。将输入事件放入事件队列中,由专门的线程负责处理,避免主线程被阻塞,提高响应速度。同时,可以对事件进行分类和过滤,只处理有意义的事件,减少不必要的处理开销。
8.3 模型数据管理优化
在模型数据管理方面,采用数据缓存和索引技术。对于频繁访问的数据,可以进行缓存,减少数据的读取时间。同时,建立合适的索引结构,提高数据的查找和更新效率。
以下是一个mermaid流程图,展示了交互式软件架构优化的主要流程:
graph TD;
A[开始] --> B[分析性能瓶颈];
B --> C{是否是视图更新问题};
C -- 是 --> D[采用高效更新算法和双缓冲技术];
C -- 否 --> E{是否是控制器事件处理问题};
E -- 是 --> F[使用事件队列和多线程技术];
E -- 否 --> G{是否是模型数据管理问题};
G -- 是 --> H[采用数据缓存和索引技术];
G -- 否 --> I[无明显瓶颈];
D --> J[测试性能];
F --> J;
H --> J;
J --> K{性能是否提升};
K -- 是 --> L[结束];
K -- 否 --> B;
9. 未来发展趋势
随着技术的不断发展,交互式软件架构也将面临新的挑战和机遇。以下是一些可能的未来发展趋势:
9.1 人工智能与机器学习的融合
人工智能和机器学习技术将在交互式软件中得到更广泛的应用。例如,通过机器学习算法对用户的输入行为进行分析,预测用户的需求,提供更个性化的交互体验。同时,人工智能可以用于自动优化软件的性能,根据不同的使用场景和用户需求,动态调整软件的参数和策略。
9.2 虚拟现实与增强现实的应用
虚拟现实(VR)和增强现实(AR)技术的发展将为交互式软件带来全新的交互方式。在VR和AR环境中,用户可以通过手势、语音等自然交互方式与软件进行互动,软件需要能够实时处理这些复杂的输入信息,并提供沉浸式的体验。这将对交互式软件的架构设计和性能提出更高的要求。
9.3 跨平台和云端计算
随着移动设备和云计算的普及,交互式软件需要具备跨平台的能力,能够在不同的操作系统和设备上运行。同时,云端计算可以提供强大的计算资源和数据存储能力,软件可以将部分计算任务和数据存储到云端,减轻本地设备的负担,提高软件的可扩展性和性能。
10. 结论
交互式软件架构是一个复杂而又关键的领域,它涉及到模型、视图、控制器、窗口系统等多个组件的协同工作。通过深入理解这些组件的功能和实现策略,我们可以设计和开发出高效、易用的交互式软件。
在实际应用中,我们需要根据具体的需求和场景,选择合适的架构和优化策略。同时,关注未来的发展趋势,不断探索新的技术和方法,以适应不断变化的用户需求和市场环境。
希望本文能够帮助读者更好地理解交互式软件架构,为相关的学习和实践提供有益的参考。如果你对交互式软件架构有更多的想法或经验,欢迎分享和交流。
超级会员免费看

被折叠的 条评论
为什么被折叠?



