第十九章 策略和层级1

软件系统是策略的陈述(statements)。事实上,就其核心而言,这实际上就是一个计算机程序。计算机程序是将输入转换为输出的策略的详细描述。

在大多数非平凡系统中,这一策略可以分解为许多不同的较小的策略陈述。其中一些陈述将描述如何计算特定的业务规则。其他的陈述将描述如何格式化某些报告。还有一些将描述如何验证输入数据。

开发软件架构的一部分技巧是仔细地将这些策略彼此分开,并根据其变化的方式对它们进行重新组合。由于相同的原因而变化的策略同时处于同一层级,并且属于同一个组件。由于不同原因或不同时间而改变的策略处于不同的层级,应该分为不同的组件。

架构艺术通常涉及将重组组件组成一个有向无环图。图形的节点是包含同层策略的组件。有向边是这些组件之间的依赖关系。他们连接不同层级的组件。

这些依赖关系是源代码时,编译时的依赖。在Java中,它们是import语句。在C#中,他们using语句。在Ruby中,它们是require语句。它们是编译器运行所必需的依赖关系。

在一个好的架构中,这些依赖关系的方向是基于它们连接的组件的层级。在任何情况下,低层组件的设计都依赖于高层组件。

层级

对“层级”的严格定义是“输入与输出的距离”。策略离系统的输入和输出距离越远,它的层级越高。管理输入与输出的策略是系统中最低层的策略。

图19.1中的数据流程图描述了一个简单的加密程序,它从输入设备读取字符,使用表格翻译字符,然后将翻译后的字符写入输出设备。数据流显示为曲线实心箭头。较正确设计的源代码依赖关系显示为比直的虚线。

图19.1一个简单的加密程序

Translate组件是这个系统中最高级的组件,因为它是离输入和输出最远的组件。

请注意,数据流和源代码相关性并不总是指向相同的方向。这也是软件架构艺术的一部分。我们希望源代码依赖关系从数据流中解耦然后在层级上耦合。

通过编写像这样的加密程序来创建不正确的架构会很容易:

function encrypt() {
    while(true)
        writeChar(translate(readChar()));
}

这是不正确的架构,因为高层加密函数依赖于较低层的readChar和writeChar函数。图19.2中的类图显示了这个系统的更好的体系结构。请注意围绕Encrypt类和CharWriter和CharReader接口的虚线边框。所有跨越边界的依赖都指向内部。这个单位是系统中最高层的元素。

图19.2 类图显示了一个更好的系统架构

ConsoleReader和ConsoleWriter在这里显示为类。他们是低层级的,因为他们接近输入和输出。

请注意,这种结构如何将高层加密策略与较层的输入/输出策略分离开来。这使得加密策略可以在各种各样的环境中使用。在对输入和输出策略进行更改时,它们不太可能影响加密策略。

回想一下,策略根据其变化的方式分组成组件。由于同样的原因和同一时间而改变的策略通过SRP和CCP分组到到一起。较高层的策略(距离输入和输出最远的策略)往往变化频率较小,而且变化的原因较于较低层的策略变化的原因更重要。较低层的策略(与输入和输出最为接近的政策)往往会经常变化,而且更为紧迫,但原因却不那么重要。

例如,即使在加密程序的一个简单的例子中,IO设备改变的可能性比加密算法改变的可能性要大得多。如果加密算法确实发生了变化,那么可能会比更改其中一个IO设备的原因更为实质性。

保持这些策略是分开的,所有的源代码依赖指向更高层策略的方向,从而减少了更改的影响。系统最底层的微小而紧急的变化对更高,更重要的层级几乎没有影响。

看待这个问题的另一种方法是注意,较低层组件应该是更高层组件的插件。图19.3中的组件图显示了这种排列。Encryption组件对IODevices组件一无所知;IODevices组件依赖于Encryption组件。

图19.3 低层组件应该插入到更高层的组件中

小结

在这一点上,这种策略讨论涉及到单一责任原则,开闭原则,共同封闭原则,依赖倒置原则,稳定依赖原则和稳定抽象原则的混合体。回头看看是否可以确定每个原则的用途,以及为什么。

1. POLICY AND LEVEL,policy翻译为策略,下同

results matching ""

    No results matching ""