软件设计原则(下)

在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的无效信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。

0 前言

上一篇的3个设计原则主要关注的是 职责 ,本篇介绍的3个设计原则主要关注的是 变化

1 开闭、依赖倒转、里氏替换

1.1 开闭原则

开闭原则的定义是:对扩展开放,对修改关闭。

修改指的是修改原有代码;扩展指的是新增代码。对于业务需求的变更(修改或者新增),尽量使用扩展的方式,而不是修改原有代码的方式实现。因为这种方式是最安全的,也是最便捷的。

开闭原则本质上关注的是如何处理变化 —— 将变化集中处理(把兔子关到笼子里,并不是杀掉兔子)。

将变化集中的另外一层含义是保留稳定部分不动。具体思路是将变化和稳定分离开,然后只需要关注变化部分。

用PCA的思想来分析这个过程,就是找到一个维度方向(x轴),该方向上变化的方差是最大的(变化区分度更大,也就意味着代码更容易扩展),垂直x轴方向的维度(y轴)代表稳定,由于稳定的代码我们不需要经常改动,所以y轴可以舍弃不进行分析,这样就做到了降维的效果。

所以,开闭原则最底层的思想是 降维 。分割就是为了达到降维的目的。

1.2 依赖倒转原则与面向接口编程

1.2.1 两者关系
  1. 关系

面向接口编程就会导致依赖倒转,遵循了依赖倒转原则写代码就是面向接口编程。

  1. 约束

所以,特意区分这两个概念没有价值,掌握两者对编码的约束就好了。

高层模块不应该依赖低层模块,两者都应该依赖抽象。抽象不应该依赖细节;细节应该依赖抽象。

Tips:对高层模块和低层模块做一个解释:如果把低层模块比作积木,那高层模块就是由积木拼成的房子。

  1. 好处
  • 可以充当“风险堤坝”,防止一个模块内的“坏代码”导致的风险溢出和扩散。

  • 方便接口测试和TDD(Test-Driven Development),以及问题定位。

  • 方便团队协作开发。

1.2.2 泛化接口的概念

面向接口编程、面向协议、面向锲约编程说的都是一回事儿——都是 面向抽象编程

抽象是一枚硬币,模块间稳定的联系和模块内灵活的变化是这枚硬币的两面。

接口要稳定,实现要灵活。稳定与变化就可兼得。

在代码中稳定的东西一般都是抽象的东西,太细节的东西是很容易变化的,所以依赖抽象才会使两个模块或类之间的联系变得稳定,但又是因为抽象,没有把联系约束的很死板,使得模块或类真正实现时又可以变得很灵活,可以有多种不同的实现方式(这不就是多态么,后文会再次提到)。

代码越抽象就意味着代码越灵活,也意味着代码的复用性和扩展性更强。

抽象的协议都可以作为接口,并不一定指Java中的Interface这类的狭义接口。

接口不光可以是方法接口,也可以是纯数据接口。比如各种通信协议就是纯数据接口。

接口也并不一定只存在于类与类之间或者模块和模块之间,还可以存在于方法与方法之间或者系统与系统之间。也就是说可以有不同粒度的接口。

接口也可以是一个模板。活字印刷就是很好的例子。

1.3 里氏替换原则与多态

1.3.1 两者关系

里氏替换原则为使用多态的正确性提供了保证。

里氏替换原则就是要程序员保证只要父类能出现的地方,就可以使用子类替换,而且不会产生任何错误和异常。

而多态就是调用者使用父类(或接口)引用时调用父类(或接口)内的方法,但由于引用指向的子类不同而导致调用方法的细节实现不同。

有一种提法是静态多态和动态多态。编译期的绑定(前绑定)就是静态多态。运行时绑定(后绑定或延迟绑定)就是动态多态。

1.3.2 泛化多态的概念

接口相同,内部实现不同就是泛化的多态。

如果泛化多态的概念,那解决扩展问题的思路就可以豁然打开了。

  1. 可以使用子类覆写父类虚函数的狭义多态实现方法。
  2. 可以使用函数指针动态绑定(狭义多态就是使用函数指针实现的)。
  3. 可以使用通用数据类型(void *配合共用体;或者使用C++17开始支持的std::any)。
  4. 可以使用命令字的方式。
  5. 可以使用字符串对象指向不同的配置文件(使用metadata的方式)。

1.4 三者关系

开闭原则讲的是面对业务变化我们要朝哪个方向走;依赖倒转原则为“开闭”中的 “闭” 提供了一种解决方案,泛化的接口则包含了所有的解决方案;里氏替换原则为开闭中的 “开” 提供了一种解决方案,泛化的多态则包含了所有的解决方案。

2 最佳实践

2.1 运用上述原则的关键

关键是对业务把控能力很强,抽象出的接口不光能兼顾当前功能,还能兼容新增业务。

能够将接口标准化,则需要对业务乃至整个行业都有精准和深入的理解。

2.2 可参考的设计经验

  1. 泛化接口+泛化多态+工厂

    可以将变化约束在2个地方:新增类和修改“工厂”。

  2. 泛化接口+泛化多态+依赖注入

    可以将变化约束在2个地方:新增类和外部配置文件。

  3. 其它设计模式的灵活应用

2.3 编码阶段如何落地

  1. 设计类时先设计接口。
  2. 设计接口时要尽量抽象和通用。
  3. 父类不要调用子类方法。
  4. 子类只覆写(不要重载)父类虚函数。如果不是写库,无论什么业务场景都不建议使用函数重载,太容易用混。

3 总结

本篇的3个设计原则非常重要,指导了几乎所有的设计模式。

此外,牢记抽象稳定变化接口多态 这几个关键词。

恭喜你又坚持看完了一篇博客,又进步了一点点!如果感觉还不错就点个赞再走吧,你的点赞和关注将是我持续输出的哒哒哒动力~~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穿越临界点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值