关于软件架构设计的笔记-开闭原则及层间依赖关系设计

一.前言

设计良好的计算机软件应该是易于扩展,同时抗拒修改。

这就是著名的开闭原则(OCP)。

换句话说,一个设计良好的计算机系统应该在不需要修改的前提下就可以轻易被扩展。

其实这也是我们研究软件架构的根本目的。如果对原始需求的小小延伸就需要对原有的软件系统进行大幅修改,

那么这个系统的架构设计显然是失败的。

一个好的软件架构师设计师会努力将旧代码的修改量降至最小,甚至为0。那么该如何实现这一点呢?

二.示例

我们可以先将满足不同需求的代码分组(即SRP:单一职责原则),然后再来调整这些分组之间的依赖关系(即DIP:依赖反转原则)。

如下:利用SRP处理数据流的示例。

这里的核心就是将应用生成报表的过程拆成两个不同的操作。

即先计算出报表数据,再生成具体的展示报表(分别以网页及纸质的形式展示)。
接下来,我们就该修改其源代码之间的依赖关系了。

这样做的目的是保证其中一个操作被修改之后不会影响到另外一个操作。

同时,我们所构建的新的组织形式应该保证该程序后续在行为上的扩展都无须修改现有代码。

具体实现的结构如下图2(关键图,后续分析围绕此图):

 在这个图中,左上角的组件是Controller,右上角是Interactor,右下角是Database,左下角则有四个组件分别用于代表不同的Presenter和View。

用<I>标记的类代表接口,用<DS>标记的则代表数据结构;开放箭头指代的是使用关系,闭合箭头则指代了实现与继承关系。

 

首先,我们在图2中看到的所有依赖关系都是其源代码中存在的依赖关系。

这里,从类A指向类B的箭头意味着A的源代码中涉及了B,但是B的源代码中并不涉及A。

因此在图2中,FinancialDataMapper在实现接口时需要知道FinancialDataGateway的实现,而FinancialDataGateway则完全不必知道FinancialDataMapper的实现。

其次,这里很重要的一点是这些双线框的边界都是单向跨越的。

也就是说,上图中所有组件之间的关系都是单向依赖的,如图3所示,图中的箭头都指向那些我们不想经常更改的组件。

也就是说,如果A组件不想被B组件上发生的修改所影响,那么就应该让B组件依赖于A组件。

图3如下:

 从以上内容我们可以看出,Interactor组件是整个系统中最符合OCP的。

发生在Database、Controller、Presenter甚至View上的修改都不会影响到Interactor。

为什么Interactor会被放在这么重要的位置上呢?

因为它是该程序的业务逻辑所在之处,Interactor中包含了其最高层次的应用策略。

其他组件都只是负责处理周边的辅助逻辑,只有Interactor才是核心组件。

虽然Controller组件只是Interactor的附属品,但它却是Presenter和View所服务的核心。

同样的,虽然Presenter组件是Controller的附属品,但它却是View所服务的核心。

 

另外需要注意的是,这里利用“层级”这个概念创造了一系列不同的保护层级。

譬如,Interactor是最高层的抽象,所以它被保护得最严密,而Presenter比View的层级高,但比Controller和Interactor的层级低。

 以上就是我们在软件架构层次上对OCP这一设计原则的应用。

软件架构师可以根据相关函数被修改的原因、修改的方式及修改的时间来对其进行分组隔离,

并将这些互相隔离的函数分组整理成组件结构,使得高阶组件不会因低阶组件被修改而受到影响。

 

依赖方向的控制
刚刚在图2中所看到的复杂度是我们想要对组件之间的依赖方向进行控制而产生的。
例如,FinancialReportGenerator和FinancialDataMapper之间的FinancialDataGateway接口是为了反转Interactor与Database之间的依赖关系而产生的。

同样的,FinancialReportPresenter接口与两个View接口之间也类似于这种情况。

 

信息隐藏
当然,在图2中,FinancialReportRequester接口的作用则完全不同,它的作用是保护FinancialReportController不过度依赖于Interactor的内部细节。

如果没有这个接口,则Controller将会传递性地依赖于FinancialEntities。
这种传递性依赖违反了“软件系统不应该依赖其不直接使用的组件”这一基本原则
所以,虽然我们的首要目的是为了让Interactor屏蔽掉发生在Controller上的修改,

但也需要通过隐藏Interactor内部细节的方法来让其屏蔽掉来自Controller的依赖

 

 总结

通过以上架构设计,我们可以看出在我们日常新建或重构的项目中,也大都遵循开闭原则。

我们会尽量让系统易于扩展,同时限制其每次被修改所影响的范围。

实现方式可能包括在重构时,需要考虑了现有系统模块间的紧密程度,调用频率等各种因素进行分析系统如何划分,

以及对系统划分为一系列组件,并将这些组件间的依赖关系按层次结构进行组织,

同时使得我们的高阶组件不会因低阶组件被修改而受到影响。

 而我们最高阶的组件无疑就是业务逻辑层,它也是抽象程度最高的一层,我们会希望它尽量稳定。

 

posted @ 2023-09-09 17:32  Vincent-yuan  阅读(28)  评论(0编辑  收藏  举报