再读整洁架构之道(三)SOLID设计原则

Tommy Cheese | Jul 5, 2024 min read

再读整洁架构之道(三)SOLID原则

构建软件模块的主要目标有三个:

  • 使软件可容忍被改动
  • 使软件更容易被理解
  • 构建可在多个软件系统中复用的组件

SOLID原则的主要作用就是告诉我们如何将数据和函数组织成为类,以及如何将这些类链接起来成为程序。SOLID原则指导我们如何设计模块,在架构设计层面,我们会有其他的设计原则。

SOLID原则是指单一职责原则SRP、开闭原则OCP、里式替换原则LSP、接口隔离原则ISP和接口反转DIP。

单一职责原则SRP

SRP定义每个软件模块只对一个功能负责,只有一个理由可以让模块改变,任何一个软件模块都应该只对某一类行为者负责。

组件层面的SRP被称作共同闭包原则CCP。

倘若设计模块时没有遵循SRP会发生什么呢?一起看一个例子。

如果财务部门、人事部门、研发部门共同依赖于一个工资、工时计算的程序,程序包含三个函数:

  • CalculateSalary:计算工资;
  • CalculateTime:计算工时;
  • Save:保存信息;

三个部门平静地使用着程序,突然有一天,财务部的薪资计算发生变化,因此财务部门的维护人员更改了CalculateSalary函数;然后,诡异的事情发生了,人事部门、研发部薪资也同时变化了…

开闭原则OCP

“拥抱新增、抗拒修改!”

OCP指出:如果软件系统想要更容易被改变,那么其设计就必须允许新增代码来修改系统行为,而非只能靠修改原来的代码。设计良好的计算机软件应该易于扩展,同时抗拒修改。

OCP不仅适用于类和模块的设计,也适用于组件的设计。他起源于设备无关性的概念。具体来说,我们使用依赖反转将IO设备设计成插件,这样的我们就能很轻易的新增IO设备,而不去修改原有的IO设备。

OCP的实现方式是通过将系统划分为一系列组件,并且将这些组件间的依赖关系按层次结构进行组织,使得高阶组件不会因低阶组件被修改而受到影响。

里式替换LSP原则

LSP指出:如果想用可替换的组件来构建软件系统,那么这些组件就必须遵守同一个约定,以便让这些组件可以相互替换。

LSP的本质是一个可替换性原则:如果对于每个类型是S的对象o1都存在一个类型为T的对象o2,能使操作T类型的程序P在用o1替换o2时行为保持不变,我们就可以将S称为T的子类型。

LSP可以且应该被应用于软件架构层面,因为一旦违背了可替换性,该系统架构就不得不为此增添大量复杂的应对机制。

接口隔离原则ISP

ISP指出:应该在设计中避免不必要的依赖,任何层次的软件设计如果依赖了它并不需要的东西,就会带来意料之外的麻烦。

依赖反转DIP

DIP指出该设计原则指出高层策略性的代码不应该依赖实现底层细节的代码,恰恰相反,那些实现底层细节的代码应该依赖高层策略性的代码。

通俗理解依赖反转:这需要使用控制流和源代码依赖流来说明。控制流譬如A组件流向B组件,此时B需要知道A的实现,A也需要引入B的模块,代码依赖上也就不可避免的A组件流向了B组件。此时系统行为决定了控制流,而控制流则决定了源代码依赖关系,软件架构就别无选择。但是通过引入接口,B组件此时只需要实现接口,接口引入B,A引入接口,此时A就无需引入B模块,此时的控制流和源代码依赖是反向的,也就是“依赖反转”。

依赖反转给了软件架构设计的自由,如果想要设计一个灵活的系统,在源代码层次的依赖关系中就应该多引用抽象类型(接口、抽象类等),而非具体实现。

DIP可以归纳出几条编码守则:

  • 应在代码中多使用抽象接口,尽量避免使用那些多变的具体实现类;
  • 不要在具体实现类上创建衍生类;
  • 不要覆盖(override)包含具体实现的函数;
  • 应避免在代码中写入与任何具体实现相关的名字,或者是其他容易变动的事物的名字。

(第三篇完)

comments powered by Disqus