开发者网络

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 82|回复: 1

软件设计——SOLID原则(一)

[复制链接]

1

主题

3

帖子

5

积分

新手上路

Rank: 1

积分
5
发表于 2023-1-5 20:00:57 | 显示全部楼层 |阅读模式
全文1781字,预计阅读时间3分钟
原创 | 陈华奔
要构建一个高水平的软件系统,必须要考虑系统的稳定性,可拓展性,灵活性以及健壮性。为了实现这样的设计,我们必须了解五大原则,也就是SOLID原则。本文作为该系列的第一部分将介绍其中的两大原则。
▌历史
SOLID原则由Robert C. Martin提出,开始于20世纪80年代,但是概念还不成熟,基本形态于2000年左右形成,最终于2004年前后诞生。   
▌内容
SOLID一词由五个原则的英文首字母拼接而成,这个五个原则为:

  • SRP (Single Responsibility Principle): 单一职责原则
  • OCP (Open/Closed Principle): 开闭原则
  • LSP (Liskov Substitution Principle): 里氏替换原则
  • ISP (Interface Segregation Principle): 接口隔离原则
  • DIP (Dependency Inversion Principle):依赖反转原则
▌单一职责原则
▌任何一个软件模块都应该只对某一类行为者负责。
这里的软件模块不仅是指某一个源代码文件或是一个类,在一些情况下也是指着一组紧密相关的函数和数据结构。现在,我们通过一个反面例子来理解这个原则。
无人机软件开发



图1 UAV类

如图所见,这个类的三个函数分别对应三类差别很大的行为者,这大大违反了SRP原则。

  • DetectTarget()由视觉算法工程师(组)开发,负责视觉检测相关功能
  • ControlVehicle()由控制算法工程师(组)开发,负责无人机的控制
  • SendZigbeeMsg()由嵌入式工程师(组)开发,负责无人机间的通讯
以上三个函数放在同一类中,导致了三类行为者的行为耦合在一起,容易造成三个开发者甚至三个开发组的工作互相依赖并互相影响的问题。
比方说,DetectTarget()和ControlVehicle()函数都需要计算目标无人机的位置。为了提高代码的复用率,我们可能会添加一个CalculateTargetPos()函数。



图2算法共享

接下来,视觉开发组提出一个新的算法,该算法需要修改CalculateTargtetPos()内的逻辑。然后,这个函数就被修改了,并且也通过了测试。他们觉得这个新算法性能很好,现在可以去实机测试了。然而,他们没有想到控制算法组那边也用到了这个函数,并且他们的控制算法并不需要修改CalculateTargtetPos()函数。
再之后,他们满怀信心地带着设备去做测试,而控制算法组完全没注意他们算法所依赖的函数被修改了。结果显然易见,控制算法出了故障,发生了严重的实验事故,无人机从空中坠毁。
读者不要觉得这样的事情不常见,类似的事情在各领域的软件开发中都是经常发生的事情。这些问题的根源就是因为将不同行为者所依赖的代码强行放在一处。所以,SRP就告诉我们这类代码一定要被分开。
▌里氏替换原则
▌如果对于每个类型是S的对象o1都存在一个类型为T的对象o2,能使操作T类型的程序P在用o2替换o1时行为保持不变,我们就可以将S称为T类型的子类型。
为了理解这个原则,我们还是通过一个例子来说明。
正方形/长方形问题
该问题是一个典型的违背里氏替换原则的案例。



图3正方形/长方形问题

该案例中,Sqaure类并不是Reatangle类的子类型。Rectangle类可以允许长宽分别被修改,但是Sqaure类要求长和宽被同时修改,并且数值要相等。
LSP不仅能运用在类的设计上用以规范类的继承,也逐渐演变成了一种更广泛的,指导接口与其实现方式的设计原则。现在我们再看一个LSP运用在软件架构中的反面案例。
违反LSP的案例
在无人机系统中,我们使用Zigbee通讯来传递数据。为了发送和接收数据,我们必须先规定好通讯协议。假设我们现在使用了某种协议,其中关于无人机位置的协议为以下格式。



表1通讯协议

并且我们定义了EncodePosition()和DecodePosition()接口用以对无人机位置信息进行编/解码。很明显,这两个函数内部的实现决定了所有参与无人机位置通讯的模块/硬件都必须使用相同的通讯协议,也必须用相同的编码方式来处理X,Y和Z的数据。
然后,项目需要通过地面站来发送无人机所要到达的目的地位置。但是,负责开发这个功能的开发者并没有好好看通讯协议手册,将X和Y的位置弄反了。因为诸多原因,地面站的代码无法重构。所以,架构师必须为无人机系统添加特殊用例,用以应对这个“新”的通讯协议。
当然,最简单的做法就是添加一条if语句:
if(zigbee.GetBytes().GetSourceID() == OxF2) // OxF2为地面站的SourceID然而,在代码中留下“0xF2”会给后续带来一系列潜在的意想不到的问题,甚至会导致严重的安全问题。

为了解决这个问题,我们需要在系统中添加一个“编解码表”,并且将其保存在其他地方,可能是数据库,也可能是本地文件。
SourceID格式
*X/Y/Z
0xF2Y/X/Z
然后我们需要增加一个复杂的组件来应对这个不能完全互相替换的通讯协议。

所以,在软件架构层面,一旦可替换性被破坏了,该系统架构就必须为此增加大量复杂的应对机制。
▌结语
本文介绍了SOLID原则中的两大原则——单一职责原则与里氏替换原则。单一职责原则告诉我们不要将不同的行为者所依赖的代码强行放在一起,而里氏替换原则则提醒我们一定要确保类和接口间具有可替换性。下一篇文章将介绍其余三种原则,敬请期待。
<hr/>本文由西湖大学智能无人系统实验室工程师陈华奔原创,申请文章授权请联系后台相关运营人员。

▌知乎:空中机器人前沿
▌Bilibili:西湖大学空中机器人
▌Youtube:Aerial robotics @ Westlake University
▌实验室网站:https://shiyuzhao.westlake.edu.cn
回复

使用道具 举报

0

主题

5

帖子

9

积分

新手上路

Rank: 1

积分
9
发表于 前天 00:06 | 显示全部楼层
锄禾日当午,发帖真辛苦。谁知坛中餐,帖帖皆辛苦!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|开发者网络

GMT+8, 2025-4-6 13:46 , Processed in 0.123592 second(s), 23 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表