软件设计原则(下)
Posted 穿越临界点
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了软件设计原则(下)相关的知识,希望对你有一定的参考价值。
在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的无效信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。
文章目录
0 前言
上一篇的3个设计原则主要关注的是 职责 ,本篇介绍的3个设计原则主要关注的是 变化 。
1 开闭、依赖倒转、里氏替换
1.1 开闭原则
开闭原则的定义是:对扩展开放,对修改关闭。
修改指的是修改原有代码;扩展指的是新增代码。对于业务需求的变更(修改或者新增),尽量使用扩展的方式,而不是修改原有代码的方式实现。因为这种方式是最安全的,也是最便捷的。
开闭原则本质上关注的是如何处理变化 —— 将变化集中处理(把兔子关到笼子里,并不是杀掉兔子)。
将变化集中的另外一层含义是保留稳定部分不动。具体思路是将变化和稳定分离开,然后只需要关注变化部分。
用PCA的思想来分析这个过程,就是找到一个维度方向(x轴),该方向上变化的方差是最大的(变化区分度更大,也就意味着代码更容易扩展),垂直x轴方向的维度(y轴)代表稳定,由于稳定的代码我们不需要经常改动,所以y轴可以舍弃不进行分析,这样就做到了降维的效果。
所以,开闭原则最底层的思想是 降维 。分割就是为了达到降维的目的。
1.2 依赖倒转原则与面向接口编程
1.2.1 两者关系
- 关系
面向接口编程就会导致依赖倒转,遵循了依赖倒转原则写代码就是面向接口编程。
- 约束
所以,特意区分这两个概念没有价值,掌握两者对编码的约束就好了。
高层模块不应该依赖低层模块,两者都应该依赖抽象。抽象不应该依赖细节;细节应该依赖抽象。
Tips:对高层模块和低层模块做一个解释:如果把低层模块比作积木,那高层模块就是由积木拼成的房子。
- 好处
-
可以充当“风险堤坝”,防止一个模块内的“坏代码”导致的风险溢出和扩散。
-
方便接口测试和TDD(Test-Driven Development),以及问题定位。
-
方便团队协作开发。
1.2.2 泛化接口的概念
面向接口编程、面向协议、面向锲约编程说的都是一回事儿——都是 面向抽象编程 。
抽象是一枚硬币,模块间稳定的联系和模块内灵活的变化是这枚硬币的两面。
接口要稳定,实现要灵活。稳定与变化就可兼得。
在代码中稳定的东西一般都是抽象的东西,太细节的东西是很容易变化的,所以依赖抽象才会使两个模块或类之间的联系变得稳定,但又是因为抽象,没有把联系约束的很死板,使得模块或类真正实现时又可以变得很灵活,可以有多种不同的实现方式(这不就是多态么,后文会再次提到)。
代码越抽象就意味着代码越灵活,也意味着代码的复用性和扩展性更强。
抽象的协议都可以作为接口,并不一定指Java中的Interface这类的狭义接口。
接口不光可以是方法接口,也可以是纯数据接口。比如各种通信协议就是纯数据接口。
接口也并不一定只存在于类与类之间或者模块和模块之间,还可以存在于方法与方法之间或者系统与系统之间。也就是说可以有不同粒度的接口。
接口也可以是一个模板。活字印刷就是很好的例子。
1.3 里氏替换原则与多态
1.3.1 两者关系
里氏替换原则为使用多态的正确性提供了保证。
里氏替换原则就是要程序员保证只要父类能出现的地方,就可以使用子类替换,而且不会产生任何错误和异常。
而多态就是调用者使用父类(或接口)引用时调用父类(或接口)内的方法,但由于引用指向的子类不同而导致调用方法的细节实现不同。
有一种提法是静态多态和动态多态。编译期的绑定(前绑定)就是静态多态。运行时绑定(后绑定或延迟绑定)就是动态多态。
1.3.2 泛化多态的概念
接口相同,内部实现不同就是泛化的多态。
如果泛化多态的概念,那解决扩展问题的思路就可以豁然打开了。
- 可以使用子类覆写父类虚函数的狭义多态实现方法。
- 可以使用函数指针动态绑定(狭义多态就是使用函数指针实现的)。
- 可以使用通用数据类型(void *配合共用体;或者使用C++17开始支持的std::any)。
- 可以使用命令字的方式。
- 可以使用字符串对象指向不同的配置文件(使用metadata的方式)。
- …
1.4 三者关系
开闭原则讲的是面对业务变化我们要朝哪个方向走;依赖倒转原则为“开闭”中的 “闭” 提供了一种解决方案,泛化的接口则包含了所有的解决方案;里氏替换原则为开闭中的 “开” 提供了一种解决方案,泛化的多态则包含了所有的解决方案。
2 最佳实践
2.1 运用上述原则的关键
关键是对业务把控能力很强,抽象出的接口不光能兼顾当前功能,还能兼容新增业务。
能够将接口标准化,则需要对业务乃至整个行业都有精准和深入的理解。
2.2 可参考的设计经验
-
泛化接口+泛化多态+工厂
可以将变化约束在2个地方:新增类和修改“工厂”。
-
泛化接口+泛化多态+依赖注入
可以将变化约束在2个地方:新增类和外部配置文件。
-
其它设计模式的灵活应用
2.3 编码阶段如何落地
- 设计类时先设计接口。
- 设计接口时要尽量抽象和通用。
- 父类不要调用子类方法。
- 子类只覆写(不要重载)父类虚函数。如果不是写库,无论什么业务场景都不建议使用函数重载,太容易用混。
3 总结
本篇的3个设计原则非常重要,指导了几乎所有的设计模式。
此外,牢记抽象、稳定、变化、接口、多态 这几个关键词。
恭喜你又坚持看完了一篇博客,又进步了一点点!如果感觉还不错就点个赞再走吧,你的点赞和关注将是我持续输出的哒哒哒动力~~
以上是关于软件设计原则(下)的主要内容,如果未能解决你的问题,请参考以下文章