【转载】实现软件架构质量属性的战术
原文地址:http://www.uml.org.cn/zjjs/201309043.asp
架构设计则为满足架构需求的质量属性寻找适当的战术。对如何实现特定的质量属性感兴趣。质量需求指定了软件的响应,以实现业务目标。我们感兴趣的是设计使用设计模式、架构模式或架构策略创建设计的“战术”。
是什么使一个设计具有了可移植性,一个设计具有了高性能,而另一个设计具备了可集成性?实现这些质量属性依赖于基本的设计策略。我们将对这些称之为“战术”的设计决策进行分析。战术就是影响质量属性响应控制的设计决策。战术集合称为“架构策略”。架构模式以某种方式将战术打包在一起。
系统设计是由决策集合组成。对设计师来说,每个战术都是一个设计选择。例如,其中一个战术引入了冗余,以提高系统的可用性。这是提高可用性的一个选择但是不是唯一选择。
我们将每个系统质量属性的战术组织为层次形式,但是每个层次只是为了说明一些战术,而且任何战术列表都肯定是不完成的。
1. 可用性战术
恢复和修复是可用性的重要方面,为了阻止错误发展成故障,至少能够把错误限制在一定的范围内,从而使修复成为可能。维持可用性的所有方法包括某种类型的冗余,用来检测故障的某种类型的健康监视,以及当检测到故障时某种类型的恢复。有些情况下,监视或恢复是自动进行的,有时需要手动。
我们事项考虑错误检测,然后分析错误恢复,最后讨论错误预防。
1> 错误检测
用于识别错误的3个战术是命令/响应、心跳和异常
⑴命令/响应。一个组件发出一个命令,并希望在预定义的时间内收到一个来自审查组件的响应。可以把该战术用在共同负责某项任务的一组组件内。客户机也可以使用这种战术,以确保服务器对象和到服务器的通信路径在期望的性能边界内操作。可以用一种层级形式组织“命令/响应”错误探测器,其中最底层的探测器对与其共享一个处理器的软件进程发出命令,较高层的错误探测器对较低层的探测器发出命令。与所有进程发出命令的远程错误探测器相比,这种战术所使用的通信带宽更少。
⑵心跳。一个组件定期发出一个心跳消息,另一个组件接收听该信息。如果心跳失败,则假定最初的组件失败,并通知错误纠正组件。心跳还可以传递数据。例如,自动柜员机定期向服务器发送一次交易日志。该消息不仅起到心跳的作用,而且传送了要处理的数据。
⑶异常。识别错误的一个方法就是遇到了异常。
命令/响应和心跳战术在不同的进程中操作,异常战术在一个进程中操作。异常处理程序通常将错误在语义上转换为可以被处理的形式。
2> 错误恢复
错误恢复由准备恢复和修复系统两部分组成。
⑴表决。运行在冗余处理器上的每个进程都具有相同的输入,它们计算发送给表决者的一个简单的输出值。如果表决者检测到单处理器的异常行为,那么就中止这一行为。表决算法可以是“多数规则”或“首选组件“或其他算法。该方法用于纠正算法的错误操作或者处理器的故障,通常用在控制系统。每个冗余组件的软件可以由不同的小组开发,并且在不同平台上执行。稍微好一点情况是在不同平台上开发一个软件组件,但是这样的开发和维护费用非常昂贵。
⑵主动冗余(热重启)。所有的冗余组件都以并行的方式对事件做出响应。因此他们都处在相同的状态。仅使用一个组件的响应,丢弃其他组件的响应。错误发生时,使用该战术的系统停机时间通常是几毫秒,因为备份是最新的,所以恢复所需要的时间就是切换时间。
⑶被动冗余(暖重启/双冗余/三冗余)
一个组件(主要的)对事件做出响应,并通知其他组件(备用的)必须进行状态更新。当错误发生时,在继续提供服务前,系统必须首先确保备用状态是最新的。该方法也用在控制系统中,通常情况是在输入信息通过通信通道或传感器到来时,如果出现故障必须从主组件切换到备用组件时使用。
⑷备件
备用件是计算平台配置用于更换各种不同的故障组件。出现故障时,必须将其重新启动为适当的软件配置,并对其状态进行初始化。定期设置持久设备的系统状态的检查点,并记录持久设备的所有状态变化能够使备件设置为适当的状态。这通常用作备用客户机工作站,出现故障时,用户可以离开。该战术的停机时间通常是几分钟。
⑸Shadow操作。以前出现故障的组件可以在短时间内以“shadow模式”运行,以确保在恢复该组件前,模仿工作组件行为。
⑹状态再同步。主动和被动冗余战术要求恢复的组件在重新提供服务前更新其状态。更新的方法取决于可以承受的停机时间、更新的规模以及更新所要求的消息的数量。
⑺检查点/回滚。检查点就是记录所创建的一致状态,或者是定期进行,或者是对具体事件做出响应。有时系统会以一种不同寻常的方式出现故障,可检测到其状态不一致。在这种情况下,应该使用上一个一致状态检查点和拍了快照后所发生的事务日志来恢复系统。
3> 错误预防
⑴从服务中删除。该战术从操作中删除了系统的一个组件,以执行某些活动来防止预期发生的故障。一个示例就是重新启动组件,以防止内存泄露导致故障的发生。如果从服务中删除是自动的,则可以设计架构策略来支持它。如果是人工进行的,则必须对系统进行设计以对其提供支持。
⑵事务。事务就是绑定几个有序的步骤,以能够立刻撤销整个绑定。如果进程中的一个步骤失败的话,可以使用事务来防止任何数据受到影响,还可以使用事务来防止访问相同数据的几个同时线程之间发生冲突。
⑶进程监视器。一旦检测到进程中存在着错误,监视进程就可以删除非执行进行,并为该进程创建一个新的实例,就像在备件战术中一样,初始化为某个适当的状态。
总结了上面讨论的战术。
2. 可修改性战术
可修改战术的目标是控制实现、测试和部署变更的时间和成本。把可修改性战术根据其目标进行分组。一组可修改性战术目标是减少由某个变更直接影响的数量。这组称为“局部化修改”。另一组可修改战术的目标是限制对局部化的模块的修改。这组称为“防止连锁反应”。两组之间的差别是有直接受变更影响的模块(那些调整其责任来完成变更的模块)间接受变更影响的模块(那些责任保持不变,但必须改变其实现来适应直接受影响的模块)。第三组战术的目标是控制部署时间和成本。我们把这组战术叫做“延迟绑定时间”。
1> 局部化修改。
目标是在设计期间为模块分配责任,以把预期的变更限制在一定范围内。其战术有:维持语义的一致性、预期期望的变更、泛化该模块、限制可能的选择。
⑴维持语义的一致性。语义的一致性是在模块中责任之间的关系。目标是确保所有这些责任都能够协同工作,不需要过多地依赖其他模块。该目标是通过选择具有语义一致性的责任来实现的。耦合和内聚指标是度量语义一致性的尝试,但它们遗漏了变更的上下文。相反根据一组预期的变更来度量语义一致性。其中一个子战术就是“抽象通用服务”。通过专门的模块提供通用服务通常被视为支持重用。但是抽象通用服务也支持可修改性。如果已经抽象出了通用服务,那么对这些通用服务的修改只需要进行一次,而不需要在使用这些服务的每个模块中都进行修改。此外,对使用这些服务的模块的修改不会影响到其他用户。不仅支持局部化修改,而且还能够防止连锁反应。抽象通用服务的示例就是应用框架的使用和其他中间件软件的使用。
⑵预期期望的变更。考虑所预想的变更的集合提供了一个评估特定的责任分配的方法。基本的问题是“对于每次变更,所建议的分解是否限定了为完成变更所需要修改的模块的集合?”一个相关的问题是“根本不同的变更会影响相同模块吗?”这与语义一致性有什么不同呢?根据语义一致性分配责任,假定期望的变更在语义上是一致的。预测期望变更的战术不关心模块责任的一致性,它所关心的是使变更的影响最小。在实际中很难单独使用该战术,因为不可能预期所有变更。基于此原因,我们通常结合语义一致性来使用该战术。
⑶泛化该模块。使一个模块更通用能够使它根据输入计算更广泛的功能。可以该输入看作是为该模块定义了一种语言,这可能会如同使常数成为输入参数一样简单;也可能如同把该模块实现为解释程序,并使输入参数成为解释程序的语言中的程序一样复杂。模块越通用,越有可能通过调整语言而非修改模块来进行请求变更。
⑷限制可能的选择。修改(尤其是在产品线中的修改)的范围可能非常大,因此可能会影响很多模块。限制可能的选择将会降低这些修改所造成的影响。例如,产品线的某个变化点可能允许处理器的变化。将处理器变更限制为相同家族的成员就限制了可能的选择。
2> 防止连锁反应。
修改所产生的一个连锁反应就是需要改变该修改并没有直接影响到的模块。例如,改变了模块A以完成某个特定的修改,那么必须改变模块B,这仅仅是因为改变了A,在某种意义上来说,是因为它依赖于模块A。
确定的8中类型的依赖。
① 语法。
1.数据。要使B正确编译或执行,由A产生并由B使用的数据类型或格式必须与B所假定的数据的类型或格式一致。
2.服务。要使B正确编译和执行,由A提供并且由B调用的服务的签名必须与B的假定一致。
② 语义。
1.数据。要使B正确执行,由A产生并由B使用的数据语义必须与B所假定的数据的语义一致。
2.服务。要使B正确执行,由A提供并且由B调用的服务的语义必须与B的假定一致。
③ 顺序。
1.数据。要使B正确执行,它必须以一个固定的顺序接收由A产生的数据。
2.控制。要使B正确执行,A必须在一定的时间限制内执行。
④ A的一个接口身份。A可以有多个接口。要使B正确编译和执行,该接口的身份(名称或句柄)必须与B的假定一致。
⑤ A的位置(运行时)。要是B正确执行,A运行的位置必须与B的假定一致。
⑥ A提供的服务/数据的质量。要是B正确执行,设计A所提供的数据或服务的质量的一些属性必须与B的假定一致。例如,某个特定的传感器所提供的数据必须有一定的准确性,以使B的算法能够正常运行。
⑦ A的存在。要是B正常执行,A必须存在。例如,如果B请求对象A提供服务,而A不存在并且不能动态创建,那么B就不能正常执行。
⑧ A的资源行为。要使B正常执行,A的资源行为必须与B的假定一致。这可以是A的资源使用(A使用与B相同的内存)或资源拥有(B保留了A认为属于它的资源)。
没有任何一个战术一定能够防止语义变更的连锁反应。首先讨论与特定模块的接口相关的那些战术——信息隐藏和维持现有的接口——然后讨论一个违反了依赖链的战术——仲裁者的使用。
⑴信息隐藏。信息隐藏就是把某个实体(一个系统或系统的某个分解)的责任分解为更小的部分,并选择使哪些信息成为公有的,哪些信息成为私有的。可以通过指定的接口获得公有责任。信息隐藏的目的是将变更隔离在一个模块内,防止变更扩散到其他模块。这是防止变更扩散的最早的技术。它与“预期期望的变更有很大关系”,因为它使用那些变更作为分解的基础。
⑵维持现有的接口。如果B依赖于A的一个接口的名字和签名,则维持该接口及其语法能够使B保持不变。当然如果B对A有语义依赖性,那么该战术不一定会起作用,因为很难屏蔽对数据和服务的含义的改变。此外,也很难屏蔽对服务质量、数量质量、资源使用和资源拥有的依赖性。还可以通过将接口与实现分离来实现该接口的稳定性。这使得能够创建屏蔽变化的抽象接口。变化可以包含在现有的责任中,或者可以通过用模块的一个实现代替另一个实现来包含变化。
实现该战术的模式包括:
1.添加接口。大多数编程语言允许多个接口。可以通过新接口提供最新的可见的服务或者数据,从而使得现有的接口保持不变并提供相同的签名。
2.添加适配器。给A添加一个适配器,该适配器把A包装起来,并提供原始A的签名。
3.提供一个占位程序A。如果修改要求删除A,且B仅依赖于A的签名,那么为A提供一个占位程序能够使B保持不变。
⑶限制通信路径。限制与一个给定的模块共享数据的模块。也就是说,减少使用由该给定模块所产生的数据的模块的数量,以及产生由该模块所使用的数据的模块的数量。这会减少连锁反应,因为数据产生/使用引入了导致连锁反应的依赖。
⑷仲裁者的使用。如果B对A具有非语义的任何类型的依赖,那么,在A和B之间插入一个仲裁者是有可能的,以管理与该依赖相关的活动。所有这些仲裁者都有不同的名字,但我们将根据列举的依赖类型对每个仲裁者进行讨论。如前所述,在最糟糕的情况下,仲裁者不能补偿语义变化。仲裁者是:
1.数据(语法)。存储库充当数据的生产者和使用者之前的仲裁者。存储库可以把A产生的语法转换为符合B的语法。一些发布/订阅模式(那些具有通过中央组件的数据流的模式)也可以把该语法转换为符合B的语法。MVC和PAC模式把一种形式的数据(输入输出设备)转换为另一种形式的数据(由MVC和PAC中的抽象所使用的形式)。
2.服务(语法)。正面、桥、调停者、策略、代理和工厂模式都提供了把服务的语法从一种形式转换为另一种形式的仲裁者。因此,可以使用他们防止A的变化扩散到B。
1. A的接口的身份。可以使用经纪人模式屏蔽一个接口的身份中的变化。如果B依赖于A的一个接口的身份并且该身份发生了变化,通过向经纪人添加该身份,并使该经纪人与A的新身份进行连接,B可以保持不变。
2. A的位置(运行时)。名称服务器能够使A的位置发生变化,且不影响B。A负责在名称服务器中注册其当前的位置,B从名称服务器中检索该位置。
3. A的资源行为或由A控制的资源(运行时)。资源管理器是一个负责进行资源非配的仲裁者。某些资源管理器(例如那些基于实时系统中速率单调性分析的管理器)可以保证满足在某些限制条件中的所有请求。当然,A必须把对该资源的控制转让给资源管理器。
4. A的存在。工厂模式能够根据需要创建实例,因此B对A的存在的依赖性由该工厂的操作来满足。
3>推迟绑定时间。
可修改性场景包括通过减少需要修改的的数量不能满足的两个元素—部署时间以及允许非开发人员进行修改。推迟绑定时间支持这两个场景,但需要提供额外的基础结构来支持后期绑定。
可以把各个时间决策绑定到执行系统中。我们讨论一下那些影响部署时间的决策。系统的部署由某个过程来规定。当修改由开发人员进行时,通常会有一个测试和分布过程,该过程确定进行改变和该改变对最终用户可用之间的时间延迟。在运行时绑定意味着系统已经为该绑定做好了准备,并且完成了所有的测试和分配步骤。推迟绑定时间还能够使最终用户或系统管理员进行设置,或提供影响行为的输入。
许多战术的目的是在载入时或运行时产生的影响,如下所示:
1. 运行时注册支持即插即用操作,但需要管理注册额外开销。例如,发布/订阅注册可以在运行时或载入时实现。
2.配置文件的目的是在启动时设置参数。
3. 多态允许方法调用的后期绑定。
4.组件更换允许载入时间绑定。
5.遵守已定义的协议允许独立进程的运行时绑定。
3. 性能战术
性能战术的目标就是对在一定的时间限制内到达系统的事件生成一个响应。事件到达后系统或者对该事件进行处理,或者由于某些原因处理被阻塞。下面是产生响应时间的两个基本因素:资源消耗和闭锁时间
资源消耗:包括CPU、数据存储、网络通信带宽和内存,但它也可以包括由设计中的特定系统所定义的实体。例如必须对缓冲器进行管理,并且对关键部分的访问必须是按顺序进行的。事件可以是各种类型的,每种类型的事件都经过了一个处理序列。
闭锁时间:可能会由于资源争用、资源不可用或者计算依赖于另外一个还不能得到的计算结果而导致计算不能使用某个资源,从而阻止了计算的进行。
1.资源争用。这些事件可能是单个流,也可能是多个流。争用同一个资源的多个流或相同流中争用同一个资源的不同事件会增加等待时间。
2.资源的可用性。即使没有争用,如果资源不可用,计算也无法进行下去。资源离线、组件故障、或其他原因都会导致资源不可用。在任何情况下,设计师都必须确定资源不可用可能会导致急剧增加等待时间的位置。
3.对其他计算的依赖性。计算可能必须等待,因为它必须与一个计算的结果同步,或者是因为它在等待它所启动的一个计算的结果。例如,它可能会从两个不同的源读取信息,如果这两个源是按顺序读取的话,等待时间将会比并行读取高。
1> 资源需求。
事件流是资源需求的源。需求的两个特征是:资源流中的事件之间的时间(在事件流中多长时间进行一次请求);每个请求所消耗的资源是多少。
减少等待时间的一个战术就是减少处理一个事件流所需要的资源。方法如下:
1. 提高计算效率。处理事件或消息中的一个步骤就是应用某个算法。改进在关键的地方所使用的算法将减少等待时间。有时可以用一种资源换取另一种资源。例如,可以把仲裁者数据保存在存储库中,也可以重新生成,这取决于时间和空间资源的可用性。该战术通常用在处理器上,但用在其他资源上也是有效的,如磁盘。
2. 减少计算开销。如果没有资源请求,就可以减少处理需求。
减少等待时间的另外一个战术就是减少所处理事件的数量。可以用一下方式进行:
1.管理事件率。如果可以降低监视环境变量处的取样频率,就可以减少需求。如果系统进行了超量设计的话,这样做是不可行的,其他时候使用不必要的高采样率来建立多个流之间的和谐周期。也就说,某个流或事件被过采样,以使他们可以被同步化。
2.控制采样频率。如果没有对外部生成的事件的到达进行控制,则可以用一个较低的频率对排队的请求进行采样,这样可能会导致请求的丢失。
用于减少或管理需求的其他战术包括控制资源的使用。
3.限制执行时间。限制用多少执行时间对事件做出响应。有时这样做有意义,有时没有意义。对于迭代、依赖于数据的算法,限制迭代的数量就是限制执行时间的一个方法。
4.限制队列的大小。这控制了排成队列到达事件的最大数量,因此控制了用来处理到达事件的资源。
2> 资源管理
尽管不能控制对资源的需求,但对这些资源的管理会影响响应时间。下面是一些资源管理的战术。
1.引入并发。如果可以并行处理,就可以减少闭锁时间。可以通过在不同的线程上处理不同的事件流或者创建额外的线程来处理不同的活动集来引入并发。引入并发后,适当地把线程分配给资源(负载均衡)非常重要,以尽可能利用并发。
2.维持数据或计算的多个副本。客户机—服务器模式中的客户机是计算的副本。使用副本的目的是减少在中央服务器上进行所有的计算时出现的争用。高速缓存的数据通常是现有数据的一个副本,因此使用副本一致和同步就变成了系统必须承担的责任。
3.增加可用资源。速度更快的处理器、额外的处理器、额外的内存以及速度更快的网络都可以减少等待时间。在选择资源时,通常会考虑成本,但增加资源绝对也是一个减少等待时间的战术。
3> 资源仲裁
当存在资源争用时,必须对资源进行调度。我们需要对处理器、缓冲器和网络进行调度安排。设计师的目标是理解每个资源使用的特性,并选择之一致的调度策略。
从概念上讲调度策略都有两部分:优先级分配和分派。所有的调度策略都分配优先级。一些常见的调度策略为:
1.先进先出。FIFO队列同等看待对资源的所有请求,并依次对其进行处理。在FIFO队列中,一个请求可能被另一个需要很长时间来生成响应的请求阻止。只有所有请求优先级都是相同的,这就不是一个问题;但如果一些请求的优先级高于其他请求的优先级,就存在这个问题。
--固定优先级调度。
固定优先级调度为每个请求资源的源分配一个特定的优先级,并按该优先级顺序分配资源。该策略能够保证为优先级较高的请求提供更好的服务,但是,对一些优先级较低的请求来说,肯能要等待很长的时间才能得到服务,因为它前面有很多优先级较高的请求。3个常见的优先级策略为:
⑴语义重要性。每个流都根据生成它的任务的某个与领域特性被静态地分配一个优先级。这种调度在大型机系统中,其中领域特性是任务启动的时间。
⑵时限时间单调。时限时间单调是一种静态优先级分配,它将较高的优先级分配给具有较短时限时间的流。在调度的不同优先级流具有实时时限时间时,使用该调度策略。
⑶速率单调。速率单调是周期流的一种静态优先级分配,它将较高的优先级分配给具有较短周期的流。该调度策略是时限时间单调的一种特殊情况,但它更为我们所熟知,操作系统对此提供支持的可能性较大。
--动态优先级调度
⑴轮转。轮转是一种调度策略,它对请求进行排序,然后在允许的时候,把资源分配给该排序中的下一个请求。轮转的一个特殊形式就是循环执行,在循环执行中,资源分配是每隔一个固定的时间进行的。
⑵时限时间最早优先。时限时间最早优先根据具有最早的视线时间的挂起请求来分配优先级。
--静态调度。循环执行调度是一种调度策略,在该策略中,离线确定先占点和资源分配顺序。
4. 安全性战术
安全性战术分为:与抵抗攻击有关的战术、与检测攻击有关的战术以及从攻击中恢复有关的战术。给门装锁就是在抵抗攻击,在房子中放一个运动传感器就是在检测攻击,给房子上保险就是从攻击总恢复。
1> 抵抗攻击。
我们把认可、机密性、完整性和保证确定为目标。可以组合使用下面的战术来实现这些目标。
1.对用户身份验证。身份验证能够保证进行访问的用户或远程计算机确实是它所声称的用户或计算机。密码、一次性密码、数字证书以及生物识别均提供身份验证。
2.对用户进行授权。授权能够保证经过了身份验证的用户有权访问和修改数据或服务。这通常通过在系统中提供一些访问控制模式进行管理。可以对单个用户进行访问控制,也可以对某一类用户进行访问控制。也可以根据用户分组、用户角色或个人列表定义用户类。
3.维护数据的机密性。应该对数据进行保护,以防止未经授权的访问。一般通过对数据和通讯链路进行某种形式的加密来实现机密性。另一方面,通信链路一般不具有授权控制,对于通过公共可访问的通信链路传数据来说,加密是唯一的保护措施。对基于web的链路,可以通过VPN或者SSL来实现该链路。
4.维护完整性。应该如期提供数据,数据中可能有冗余信息、如校验或哈希值,他们可以与原始数据一起进行加密,也可以单独加密。
5.限制暴露的信息。攻击者通常会利用暴露的某个弱点来攻击主机上的所有数据和服务。设计师可以设计服务在主机上的分配,以使只能在每个主机上获得有限的服务。
6.限制访问。防火墙根据消息源或目的地端口来限制访问。来自未知源的消息可能是某种形式的攻击。限制对已知源的访问并不总是可行的,例如,一个公共网站上可能会有来自未知源的请求。这种情况中使用一个配置就是所谓的解除管制区。
2> 检测攻击。
检测攻击通常通过“入侵检测”系统进行。
3> 从攻击中恢复。
可以把从攻击中恢复的战术分为恢复状态相关的战术和与识别攻击者相关的战术。在将系统或数据恢复到正确状态时所使用的战术与用于可用性的战术发生了重叠,因此他们都是从不一致的状态恢复到一致状态。差别就是要特别注意维护系统管理数据的冗余副本,如密码、访问控制列表、域名服务和用户资料数据。
用于识别攻击者的战术就是“维持审计追踪”。审计追踪就是应用到系统中的数据的所有事务和识别信息的一个副本。可以使用审计信息开追踪攻击者的操作。支持认可并支持系统恢复。
5. 可测试性战术
可测试性战术目标是允许在完成一个软件开发的增量后,轻松地对软件进行测试。我们对两类用于测试的战术进行讨论:提供输入并捕获输出;内部监视。
1> 输入/输出
1.记录回放。记录回放是指捕获跨接口的信息,并将其作为测试专用软件的输入。在正常操作中操作中跨一个接口的信息保存在某个存储库中,它代表来自一个组件的输出和传到一个组件的输入。记录该信息使得能够生成对其中一个组件的测试输入,并保存用于以后比较测试输出。
2.将接口与实现分离。将接口与实现分离允许实现的代替,以支持各种测试目的。占位实现允许在缺少被占用的组件时,对系统的剩余部分进行测试。用一个组件代替某个专门的组件能够使被代替的组件充当系统剩余部分的测试工具。
3.特化访问路线/接口。具有特化的测试接口允许通过测试工具并独立于其正常操作,来捕获或指定组件的变量值。例如,可以通过允许特化的接口提供原数据,测试工具利用该接口推动其活动。
2> 内部监视
内置监视器。组件可以维持状态、性能负载、容量、安全性或其他可通过接口访问的信息。此接口可以是该组件的一个永久接口,也可以是通过instrumentation技巧临时引入的接口,如面向方面编程或预处理程序宏。一个常见的技巧就是当监视状态被激活时记录事件。监视状态实际上会增加测试工作,因为随着监视的关闭,可能必须重复测试。尽管额外测试需要一定的开销,但这却使组件活动的可见性得以提高,这样做是值得的。
6. 易用性战术
易用性与用户完成期望任务的难易程度以及系统为用户提供的支持种类有关。有两种类型的战术支持易用性,每种战术所针对的是两种类别的“用户”。第一类是运行时,包括那些在系统运行期间支持用户的战术。第二类基于用户接口设计的迭代特性,它在设计时支持接口开发人员。
1> 运行时战术。
一旦系统执行,就可以通过为用户提供关于系统正在做什么的反馈,以及用于提供发出基于易用性命令的能力来增强易用性。例如,在纠错或更高效的操作中,“取消”、“撤销”、“聚合”和“显示多个视图”均为用户提供支持。
1.维持任务的一个模型。这种情况下,所维持的模型是关于任务的信息。任务模型用于确定上下文,以使该系统了解用户试图做什么,并提供各种协助。例如,知道句子通常以大写字母开头能够使应用程序纠正该位置的小写字母。
2.维持用户的一个模型。维持的模型是关于用户的信息。它确定了用户对该系统的了解,用户在期望的响应时间方面的行为,以及特定于某个用户或某类用户的其他方面。例如,维持用户模型能够使系统以用户可以阅读月面的速度滚动页面。
3.维持系统的一个模型。所维持的模型就是关于系统的信息。它确定了期望的系统行为,以便为用户提供适当的反馈。系统模型反馈预测了诸如完成当前活动所需要时间的项目。
2> 设计时战术。
在测试过程中,通常会频繁修改用户接口。也就是说,易用性工程师将为开发人员提供对当前接口设计的修改,开发人员将实现这些修改。这导致了对语义一致的可修改性的求精。
将用户接口与应用的其余部分分离开来。局部化所期望的变更是语义一致的一个基本原理。因为在开发中和部署后,我们预计用户接口频繁发生变化,因此单独维护用户接口代码将会变更局部化在某个地方。开发用于实现该战术并支持用户接口修改的软件架构模式为:
模型——视图——控制器
表示——抽象——控制
Seeheim
Arch/Slinky
7. 战术与架构模式的关系
Active Objcet设计模式将方法执行从方法调用中分离出来,以增强并发,并简化对驻留在其自身控制线程中的对象的同步访问。
该模式由6个元素组成:代理,它提供了允许客户对主动对象调用公共访问方法的接口;方法请求,它定义了用于执行主动对象的方法的一个接口;激活接口,它维持了挂起方法请求的一个缓冲器;调度程序,它决定接下来执行什么方法请求;附属,他定义可建模为主动对象的行为和状态;将来,它允许客户获得方法调用的结果。
该模式的动机就是增强并发性——这是一个性能目标。因此其主要目的就是实现“引入并发“性能战术。然而,还要注意该模式包含的其他战术。
信息隐藏(可修改性)。每个元素都选择了它将实现的责任,并将其实现隐藏在接口后面。
仲裁者(可修改性)。该代理充当着把变化缓冲到方法调用中的仲裁者。
绑定时间(可修改性)。主动对象模式假定对该对象的请求在运行时到达该对象。然而,并没有确定客户机与代理的绑定时间。
调度策略(性能)。调度程序实现一些调度策略。
对设计师来说,分析过程包括理解嵌入在实现中的所有战术;设计过程包括在关于哪些战术最和将实现系统期望的目标方面,做出一个明智的选择。
8. 架构模式和样式
软件中架构模式与建筑物中的架构样式类似,它由几个将他们组合起来以维持架构完整性的关键特性和规则组成。架构模式由以下几个因素确定:
一组元素类型(如数据存储库或计算数学函数的组件)
指出其相互关系的元素的拓扑布局。
一组语义限制(如管道——过滤器样式中的过滤器是纯数据转化器——他们以增量形式将其输入流转换为输出流,但并不控制上游流或下游元素)。
一组交互机制(如子例程调用、事件——调阅者、黑板)、他们确定元素将如何通过允许的拓扑进行协调。
架构模式和战术之间是什么关系呢?正如已经说明的那样,我们把战术看作是设计的基本“构建块”,并根据该战术创建架构模式和策略。