3.3设计原则和启发式

启发式方法是用于以有效利用资源的方式解决问题的方法和程序。

根据起始点和要解决的问题,本章中提到的各种方法和程序可能会有所帮助,例如自上而下和自下而上、“分而治之”或关注点分离。

与清晰展示特定软件架构应如何构建的参考架构不同,架构原则[VA++09]代表了经过验证的基本原则,但没有提供关于如何在特定情况下使用它们的信息。

在这里提到的大多数原则中,两个主要问题起着重要作用。它们是:降低架构的复杂性和提高架构的灵活性(和适应性)。

3.3.1自顶而下和自底向上

图3-4自上而下和自下而上

自上而下的方法从问题开始,依次将其分解为更小的子问题,最终归结为不能再分解且可以直接解决的微型问题。

这种方法的优点是所有组件都是已知的,产生不合适结果的风险极低。然而,这些优点只有在后期才能显现出来,而且误解会在项目结束时的结果中体现出来。

表3-1Top-down

与之相反,自下而上的方法从特定的机器开始,并在其之上构建额外的“抽象机器”。开发人员在不完全了解所有系统细节的情况下就开始实施。部分解决方案相互结合,直到最终创建出一个完整的“问题解决机器”。

与自上而下的方法相比,自下而上的方法能快速取得成果,风险能在早期被识别。另一方面,部分结果可能对后续步骤不适用。

表3-2Bottom-up

这两种方法不是互斥的,而是可以互补的。

3.3.2 层级分解

3.3.2.1分而治之

“分而治之”原则在 IT 的许多分支中都有应用,它描述了一种简化的方法,将一项任务分解为越来越小的部分任务,直到这些任务的复杂性达到可管理的水平。这个原则也在众多算法中被使用,并利用了这样一个事实:当问题被分解为较小的子问题时,解决问题所需的努力会减少。

与自上而下的设计方法有明显的相似性。一个系统或组件被分解为越来越小的、相对独立的组件,从而形成一个分层(或树型)的组件结构。

这种方法可用于封装单个或多个功能或职责,或者将问题的不同方面相互分离。

根据算法的不同,解决整体问题有多种可能的方法,例如: • 最终子问题的解决方案也是整体问题的解决方案。在二叉树中搜索时,搜索的最后一步对应于树中的适当位置。 • 部分解决方案组合形成整体解决方案。 • 根据特定标准从最佳部分解决方案中选择整体问题的解决方案。对于一些优化问题,解决方案空间被细分,并在部分解决方案中寻找最优解。然后从这些最优的部分解决方案中选择最佳解决方案作为整体解决方案。

3.3.2.2分解原则

分解是降低复杂性的重要方法[Sta11]。分解的核心原则之一是封装,否则可能导致系统各部分之间产生不期望的依赖。将复杂性封装在组件中,并将这些组件视为黑盒。组件不应对其他组件的内部结构做任何假设。 其他重要方面是低耦合和高内聚,但我们稍后会更详细地讨论这些。

不要重新发明轮子,应当复用已经建立和经过验证的结构。

迭代设计,并根据原型设计确定和评估优点和缺点。

将系统分解为尽可能独立的元素,并清晰、易懂地分离职责。

3.3.2.3“尽可能简单”原则 正如阿尔伯特·爱因斯坦曾经说过的,“把事情变得尽可能简单,但不要更简单。”

简单性有理想的效果。它使事情更容易理解,并防止问题被过度的复杂性所隐藏。简单的结构更容易理解,因此也更容易改变。任何依赖关系也能更容易确定和更容易去除。

这个原则与“适用性”一词密切相关,因为在特定情况下一定程度的复杂性可能是适当的。然而,适当使用复杂性是一个经验问题。如有疑问,应优先选择较不复杂的选项。

3.3.2.4关注点分离 关注点分离原则指出,一个问题的不同方面应该相互分离,每个子问题都应该单独处理。与许多其他原则一样,它基于“分而治之”的原则。

在设计的各个层面,从单个类到整个系统,都应该处理关注点和责任。

功能元素和技术元素的分离尤其重要,应该是一个基本目标。这样做可以确保功能抽象与特定的技术实现分离,并且允许两个方面彼此独立地进一步发展(或者更容易替换和复用单个程序元素)。另一个优点是由于改进了变更的可追溯性及其影响,从而提高了质量。

一个系统的模块化程度决定了它在多大程度上被分解并封装在自包含的构建块(模块)中。关注点分离原则可以与信息隐藏原则结合使用来实现模块化原则。模块化原则指出,应该致力于使用具有简单和稳定关系的自包含系统构建块(模块)。模块化系统的构建块应该是黑盒,对外部隐藏其内部工作方式。

3.3.3 精益接口和信息隐藏

3.3.3.1信息隐藏

信息隐藏原则由大卫·帕纳斯(David Parnas)在 20 世纪 70 年代早期提出。

正如已经解释的那样,系统的复杂性应该封装在构建块中。这在进行更改时增加了灵活性。构建块被视为黑盒;拒绝访问其内部结构,而是通过定义的接口进行访问。只应公开对任务绝对必要的总信息的子集。

3.3.3.2接口的使用

架构最重要的方面是接口以及构建块之间的关系。接口构成了整个系统的基础的一部分,并实现了系统中各个元素之间的关系。各个构建块和子系统通过接口相互通信和协作。与外部世界的通信也通过接口进行。

3.3.4定期重构和重新设计

第一次将钥匙插入新房的锁并打开门是一种美妙的体验。各个房间仍然散发着油漆工、木匠和其他人最后工作的气味。一切都很干净,厨房也很整洁。几周后,新鲜的气味消失了。如果你不定期整理、维护、修理和扔掉东西,新房子很快就会变得没有吸引力。

同样的原则适用于软件及其架构。软件通常会不断增强。如果在这个过程中不定期整理并消除粗糙的边缘,由于时间压力和错误修复而创建的附加功能将无法正确集成到基础架构中,即使是最好的软件架构也会在很短的时间内退化。软件进一步开发和翻新的成本往往很高,以至于努力不再在经济上可行。然后,从头开始成为一个不能排除的选项。

因此,有必要定期对软件进行“重构”并进行重新设计。在定义“重构”时,马丁·福勒(Martin Fowler)区分了名词“重构”和“重构”的活动: • 重构(名词)[Fow99]: “对软件的内部结构进行的更改,使其更易于理解和修改,而不改变其可观察的行为。” • 重构(动词)[Fow99]: “通过应用一系列重构来重组软件,而不改变其可观察的行为。” 重构有助于调整依赖关系,从而使增量开发变得更简单。

以一个在类中修复错误的例子来说,该类通过多重解引用以 u.getV().getW().getX().getY().getZ().doSomething() 的格式访问另一个类。应避免这种解引用链,因为它们在整个类网络中创建了直接依赖关系。在这种情况下,一种可能的重构方法是在类 U 中放置一个新的方法 getZ()。

关键是要定期花时间进行重构和重新设计,并且在整个项目计算中必须规划适当的资源。

Last updated