架构师之路 - SOLID设计准则

  • 时间:2022-03-15 15:01 作者:Brown_ 来源: 阅读:435
  • 扫一扫,手机访问
摘要:SRP 单一职责准则OCP 开闭准则LSP 里氏替换准则ISP 接口隔离准则DIP 依赖反转准则在架构之路上和代码设计上,我们肯定要明白上面的几个准则,在这几个准则的指导下,才能设计出优良的架构,才能经得住撕逼。SRP 单一职责准则SRP是五大准则里最容易被误会的一个,很多程序员根据SRP这个名字想
  • SRP 单一职责准则
  • OCP 开闭准则
  • LSP 里氏替换准则
  • ISP 接口隔离准则
  • DIP 依赖反转准则

在架构之路上和代码设计上,我们肯定要明白上面的几个准则,在这几个准则的指导下,才能设计出优良的架构,才能经得住撕逼。

SRP 单一职责准则

SRP是五大准则里最容易被误会的一个,很多程序员根据SRP这个名字想当然的认为这个准则就是指:每个模块都应该只做一件事。
但这是只是一个面向底层实现细节的设计准则,并不是SRP的一律。

在历史上,我们曾经这样形容SRP这一准则:任何一个软件模块都应该有且仅有一个被修改的起因。
在现实环境中,软件系统为了满足客户和所有者的要求,总是会面临这样那样的修改。而系统客户或者者所有者就是该设计准则中所指的‘被修改的起因’。所以也可以这样形容SRP。

任何一个软件模块都应该只对一个客户或者者系统相关者负责。
这里的‘客户’和‘系统相关者’在用词上也不完全精确,它们很有可能指的是一个或者多个客户和利益相关者,只需这些人希望对系统进行的变更类似的,即可以归为一类或者者称其为行为者。所以,SRP的最终形容变成了:

任何一个软件模块都应该只对一类行为者负责
这个软件模块指的是什么呢?可以一个源代码文件,或者者是一组紧密相关的函数和数据结构。

我们看一下下面的商品类设计


类里有三个方法

  • getPrice() 获取价格
  • getOrderList() 获取订单列表
  • getReturnList() 获取退货列表

消费者需要getPrice 函数 , 库房管理员需要getOrderList() 函数 ,售后人员 需要 getReturnList() 函数

这个类同事对三个相关者负责,可能由于商品订单函数的修改影响到售后。所以这个类违反了 单一职责准则 准则。
接下来对类按照行为拆分


商品类 对 消费者负责
订单类 对 库房管理员负责
收收类 对 售后人员负责
其中的每个类的修改都不会牵扯他人,也只对一类行为者负责,这准则极大的降低了代码的耦合。

OCP 开闭准则

OCP 开闭准则,看起来感觉很难懂,其实可以这么了解。
设计良好的软件应该易于扩展,同时抗拒修改
简单的说就是系统应该不需要修改的前提下即可以轻易扩展。这个准则是软件设计,系统架构中非常重要的准则。

接下来看一组架构


image.png

这里将PC 和WAP的数据展现放到了一个类里面,假如此时要产品要再加一种PAD的显示方式,就要修改展现代码,否则无法加入新的功能。

接下来按照OCP 准则优化


image.png

设计了一个数据运算层,也可以说是MVC中的M层,这个层主要生成格式化数据,给前台的展现层提供标准化数据,在这个结构中增加PAD展现类,无需修改任何代码,可以很容易增加。在再增加其余任何展现类都可以不用修改,从这个设计中可以看出是易于扩展,同时抗拒修改的。同时底层的数据发生变化,只用修改数据运算层,无需修改前台展现类,这样也解除了依赖,做到了依赖反转。
OCP 主要是让我们的系统已于扩展,同时限制其每次被修改的所影响的范围。

LSP 里氏替换准则

派生的子类应该是可替换基类的,也就是说任何基类可以出现的地方,子类肯定可以出现。

简单的来说就是,当你通过继承实现多态行为时,假如派生类没有遵守LSP,可能会让系统引发异常。所以请谨慎使用继承,只有确定是“is-a”的关系时才使用继承。

我们来看一个经典的错误模型。


image.png

当客户调用矩形类时:
矩形 r = new 矩形()
r.setH = 10
r.setW = 2
assert(r.area() == 20 )
很显然换成 正方形 的类,客户这样调用就会存在问题。也就是说当 矩形 出现的地方不能替换成子类 正方形,这里就违反了里氏替换准则(LSP)。

我们来看一个正确的设计模型。


image.png

在警察检查身份证号,每个中国人出生就有一个身份证号,所以这里中国人的子类,工人或者者司机都存在这个获取身份证号的方法,任何父类出现的地方子类都可以替换,这就是里氏替换准则。
LSP 可以且应该应用于原件架构的各个层面,由于一旦违反了可替换性,系统就不得不为此添加大量复杂的对应机制

ISP 接口隔离准则

接口隔离准则(ISP)表明类不应该被迫依赖他们不使用的方法。
我们先来看一个设计。

image.png

假设这里
User1 调用 op1
User2 调用 op2
User3 调用 op3
再假设这里的代码是java 这种编译语言写的,那么很显著User1尽管不用调用op2 op3,但在源代码上形成了依赖关系,这种依赖关系意味着我们队OPS 中op2所做的任何修改,即便不影响User1的功能,也会导致他需要重新被编译和部署。

这个问题可以通过接口隔离来处理。


image.png

现在User1 的源代码会依赖U1Ops 和op1 ,但不会依赖U2Ops op2,U2Ops做任何修改都不需要重新编译和部署User1。

看到这里大家是不是任务接口隔离只是对编译语言的一种优化,像PHP 和Python 就不需要这种设计呢?
这原理在软件架构中也有很大的意义。


image.png

我们来开系统S引入了框架F,框架F必需使用数据库D。那么就形成了S依赖于F,F依赖于D的关系。

在这种D中包含了F中的不需要的功能,那么这些功能也是S中不需要的。而我对D的修改会导致F可能会重新部署,接着又会导致S的重新部署。更可怕是D中的一个无关功能修改的错误,导致F和S都无法运行。
任何层次的软件设计假如依赖了它并不需要的东西,就会带来预料之外的麻烦

SRP 依赖反转准则

我们每次修改笼统接口的时候,肯定回去修改对应的具体实现,但是反过来,当我们修改具体实现时,却很修改对应的笼统接口。所以我们认为接口比实现更加的稳固。

也就是说,假如想要在软件设计上追求稳固,就必需使用稳固的笼统接口,少依赖多变的具体实现。下面,我们将该设计的准则归结为以下几条具体守则。

  • 应该在代码中多使用笼统接口,尽量避免使用那些多变的具体实现类。
  • 不要在具体实现类上创立衍生类
  • 不要覆盖包含具体实现的函数
  • 避免在代码中写入与任何具体实现相关的名字,或者者是其余容易变动的事务名称。

先不考虑依赖反转的设计,我们来看这样一个设计


image.png
<?phpclass Driver{    public function drive(BMW $bmw)    {        $bmw->run();    }}class BMW{    public function run()    {        echo "宝马上路......";    }}class Client{    public function goToWork(){        $driver = new Driver();        $bmw = new BMW();        $driver->drive($bmw);    }}

这样的设计乍一看如同也没有问题,开着宝马去上班,但是假如我们买了奔驰,想开奔驰去上班,这咋办呢?要去修改Driver类,才能使用奔驰车,这样的设计导致了代码耦合很高,具体实现类的修改,必需修改笼统逻辑。
我们按照依赖反转准则进行设计


image.png
<?phpabstract class CarBase{    public abstract function run();}class BMW extends CarBase{    public function run()    {        echo "宝马上路......";    }}class Benz extends CarBase{    public function run()    {        echo "奔驰上路......";    }}abstract class DriverBase{    public abstract function drive(CarBase $car);}class Driver extends DriverBase{    public function drive(CarBase $car)    {        $car->run();    }}class Client{    public function goToWork()    {        $driver = new Driver();                $bmw = new BMW();        $driver->drive($bmw);        /**         * 很轻松的增加车辆         */        $benz = new Benz();        $driver->drive($benz);    }}

笼统是对实现的束缚,是对依赖者的一种契约,不仅仅束缚自己,还同时束缚自己与外部的关系,其目的就是保证所有的细节不脱离契约的范畴,确保束缚双方按照规定好的契约(笼统)共同发展,只需笼统这条线还在,细节就脱离不了这个圈圈。

当学习完设计准则后,我发现依赖反转准则,其实是其余几个准则的综合,接口的设计保证了单一职责准则,依赖反转的部分实现也满足了开闭准则,通过笼统进行束缚很大程度上也是一种里氏替换准则,接口的设计又实现各个接口的隔离,这里也提现了接口隔离准则
综上所述可以得出,好的依赖隔离的设计是同时满足SOLID准则的。那么反之可以得出假如其中任意准则实现的不好,我们就要反思依赖反转能否没有做好。

总结

SOLID设计准则,看起来说的都是少量老掉牙的准则,少量工作多年的工程师,都或者多或者少的使用了其中少量准则,但其中大部分都不能一律的说出这些准则的使用场景和使用方式。这就像一个行走多年的武林人士会很多的招式,但是知其然不知其所以然。
认真的学习设计的基本功,就像郭靖大侠,一步一步的打好基础,未来遇到更大更复杂的招式都可以化繁为简,快速学习。

  • 全部评论(0)
最新发布的资讯信息
【系统环境|】2FA验证器 验证码如何登录(2024-04-01 20:18)
【系统环境|】怎么做才能建设好外贸网站?(2023-12-20 10:05)
【系统环境|数据库】 潮玩宇宙游戏道具收集方法(2023-12-12 16:13)
【系统环境|】遥遥领先!青否数字人直播系统5.0发布,支持真人接管实时驱动!(2023-10-12 17:31)
【系统环境|服务器应用】克隆自己的数字人形象需要几步?(2023-09-20 17:13)
【系统环境|】Tiktok登录教程(2023-02-13 14:17)
【系统环境|】ZORRO佐罗软件安装教程及一键新机使用方法详细简介(2023-02-10 21:56)
【系统环境|】阿里云 centos 云盘扩容命令(2023-01-10 16:35)
【系统环境|】补单系统搭建补单源码搭建(2022-05-18 11:35)
【系统环境|服务器应用】高端显卡再度登上热搜,竟然是因为“断崖式”的降价(2022-04-12 19:47)
手机二维码手机访问领取大礼包
返回顶部