定义
可修改性是有关变更的成本问题。它提出了两个关注点:
可以修改什么(制品)?
可以修改系统的任何方面 ,最常见的就是系统计算的功能、系统存在平台(硬件、操作系统和中间件等 )、系统运行的环境(它必须与之互操作的系统,它用于与其他部分进行通信的协议,等等)、系统所展示的质量属性(其性能、可靠性、甚至包括将来的可修改性)以及其容量(所支持的用户数量、同时发生的操作的数量,等等)。何时进行变更以及由谁进行变量(环境)?
最常见的就是修改源代码。也就是说,开发人员必须修改代码,对修改后的代码进行测试,然后将其部署在新版中。然而,现在不仅仅是何时变更的问题,而且还有由谁进行变量的问题。
可修改性战术
局部化修改
维持语义的一致性
语义一致性指的是模块中责任之间的关系。目标是确保所有这些责任都能够协同工作,不需要过多地依赖其他模块。该目标是通过选择具有语义一致性的责任来实现的。耦合和内聚是度量语义一致性的尝试。其中一个子战术就是”抽象通用服务“。通过专门的模块提供通用服务通常被视作支持重用。这是正确的,但抽象通用服务也支持可修改性。如果已经抽象出了通用服务,那么,对这些通用服务的修改只需要进行一次,而不需要在使用这些服务的每个模块中都进行修改。此外,对使用这些服务的模块的修改不会影响其他的用户。因此,该战术不仅支持局部化修改,而且还能够防止连锁反应。抽象通用服务的示例就是应用框架的使用和其它中间件软件的使用。预期期望的变更
考虑所预想的变更的集合提供了一个评估特定的责任分配的方法。基本的问题就是”对于每次变更,所建议的分解是否限定了为完成变量所需要修改的模块集合?“一个相关的问题是”根本不同的变更会影响相同的模块吗?“这与语义一致性有什么不同?根据语义一致性分配责任,假定期望的变更在语义上是一致的。预测期望变更战术并不关心模块责任的一致性,它所关心的是使变更的影响最小。在实际中很难单独使用该战术,因为不可能预期所有变更。基于此原因,我们通常结合一致性来使用该战术。泛化该模块
使一个模块更通用能够使它根据输入计算更广泛的功能。限制可能的选择
修改的范围可能非常大,因此可能会影响很多模块。限制可能的选择将会降低这些修改所造成的影响。
防止连锁反应
依赖性
-
语法
- 数据
要使B正确编译(或执行),由A产生并由B使用的数据类型(或格式)必须与B所假定的数据的类型(或格式)一致。 - 服务
要使B正确编译和执行,由A提供并且由B调用的服务的签名必须与B假定的一致。
- 数据
-
语义
- 数据
要使B正确执行,由A产生并由B使用的数据的语义必须与B的假定一致。 - 服务
要使B正确执行,由A产生并由B使用的服务的语义必须与B的假定一致。
- 数据
-
顺序
- 数据
要使B正确执行,它必须以一个固定的顺序接收由A产生的数据。 - 控制
要使B正确执行,A必须在一定时间限制内执行。
- 数据
A的一个接口的身份
A可以有多个接口。要使B正确编码和执行,该接口的身份必须与B假定的一致。A的位置(运行时)
要使B正确执行,A的运行时位置必须与B的假定一致。A提供的服务/数据的质量
要使B正确执行,涉及A所提供的数据或服务的质量的一些属性必须与B假定一致。如:某个特定的传感器所提供的数据必须有一定的准确性,以使B的算法能够正常运行。A的存在
要使B正常执行,A必须存在。A的资源行为
要使B正常执行,A的资源行为必须与B的假定一致。这可以是A的资源使用(A使用与B相同的内存)或资源拥有(B保留了A认为属于它的资源)。
依赖性战术
信息隐藏
信息隐藏就是把某个实体的责任分解为更小的部分,并选择使哪些信息成为公有的,哪些信息成为私有的。-
维持现有的接口
如果B依赖于A的一个接口的名字和签名,则维持该接口及其语法能够使B保持不变。
实现该战术的模式包括:添加接口
可以通过新接口提供最新的可见的服务或数据,从而使得现有的接口保持不变并提供相同的签名。添加适配器
给A添加一个适配器,该适配器把A包装起来,并提供原始A的签名。提供一个占位程序
如何修改要求删除A,且B仅依赖于A的签名,那么为A提供一个点位程序能够使B保持不变。
限制通信路径
限制与一个给定的模块共享数据的模块。也就是说,减少使用由该给定模块所产生的数据的模块数量,以及产生由该模块所使用的数据的模块的数量。-
仲裁者的使用
如果B对A具有非语义的任何类型的依赖,那么,在A和B之间插入一个仲裁者是可能的,以管理与该依赖相关在的活动。所有这些仲裁者都有不同的名字,但我们将根据列举的依赖类型对每个仲裁者进行讨论,如下:数据(语法)
存储库充当数据的生产者和使用者之间的仲裁者。存储库可以把A产生的语法转换为符合B的语法。一些发布/订阅模式也可以把该语法转换为符合B的语法。服务(语法)
正面、桥、调停者、策略、代理和工厂模式都提供了把服务的语法从一种形式转换为另一种形式的仲裁者。因此,可以使用它们防止A的变化扩散到B。A的接口的身份
可以使用经纪人模式屏蔽一个接口的身份中的变化。如果B依赖于A的一个接口的身份并且该身份发生了变化,通过向经纪人添加该身份,并使经纪人与A的新身份进行连接,B可以保持不变。A的位置(运行时)
名称服务器能够使A的位置发生变化,且不影响B。A负责在名称服务器中注册当前位置,B从名称服务器中检索该位置。A的资源行为或由A控制的资源(运行时)
资源管理器是负责进行资源分配的仲裁者。某些资源管理器可以保证满足在某些限制条件中的所有请求。当然,A必须把对该资源的控制让给资源管理器A的存在
工厂模式能够根据需要创建实例,因此B对A的存在的依赖性由该工厂的操作来满足。
推迟绑定时间
我们的可修改性场景包括通过减少需要修改的模块的数量不能满足的两个元素--部署时间以及允许非开发人员进行修改。推迟绑定时间支持这两个场景,可以在各个时间把决策绑定到执行系统中。在运行时绑定意味着系统已经为该绑定做好了准备,并且完成了所有的测试和分配步骤。
运行时注册
支持即插即用操作,但需要管理注册的额外开销。例如,发布/订阅注册可以在运行时或载入时实现。配置文件
目的是在启动时设置参数。多态
允许方法调用的后期绑定。组件更换
允许载入时间绑定。遵守已定义的协议
允许独立进程的运行时绑定。