设计模式中的黄金原则:引领你的代码风格,提升可维护性与扩展性

中国的先贤说过: 有道无术,术可求.有术无道,止于术. 术指的是技能、技术或方法,而道指的是原则、道德、智慧和理念。

西方古代的哲人也说过同样的话: 智慧之路从感性开始,却终极于理性.为什么要说设计原则呢, 因为设计模式通常需要遵循一些设计原则,在设计原则的基础之上衍生出了各种各样的设计模式。设计原则是设计要求,设计模式是设计方案,使用设计模式的代码则是具体的实现。

设计模式中主要有六大设计原则,简称为SOLID ,是由于各个原则的首字母简称合并的来(两个L算一个,solid 稳定的),六大设计原则分别如下:

1、单一职责原则(Single Responsibitity Principle)

2、开放封闭原则(Open Close Principle)

3、里氏替换原则(Liskov Substitution Principle)

4、接口分离原则(Interface Segregation Principle)

5、依赖倒置原则(Dependence Inversion Principle)

6、迪米特法则(Law Of Demter)

1) 单一职责原则

一个类应该只有一个引起它变化的原因。换言之,一个类只负责一项职责。这样可以使得类更加可维护、可扩展、可重用。

在类的设计中 我们不要设计大而全的类,而是要设计粒度小、功能单一的类

生活中的例子

首先是单一职责原则: 一个类只负责一项职责, 比如说一辆汽车的刹车踏板,它的作用就是让行进间的汽车停止的, 如果现在这个刹车踏板的功能改成了,踩一半是油门, 踩到底是刹车,大家想一下 这会出现一种什么情况呢 ? 那现在世界上会开车的人 可能就非常少了, 估计女司机应该一个都没有. 这就是单一职责原则.

代码举例: 产品提出需, 要我们设计一个计算图形面积的类,如下:

接着又提出,要能够将计算结果以JSON格式打印出来,然后我们就添加了这样一个打印JSON格式的方法

请问在该类中添加打印方法是否合理 ?

答: 显然是不合理. 如果后面产品有提出了 打印XML格式...导出到Excel... 那

这个类就会变得十分臃肿,增加了很多本不属于他的责任

那该如何设计? 答: 将打印功能单独设计一个类出来

注意: 面试官问的是一个思路,看你是不是有这种设计思维 不会关心具体代码

单一职责原则的优点包括:

  1. 提高代码的可读性和可维护性:一个类只负责一个职责,代码更加清晰,更容易理解和维护。

  2. 降低类的复杂度:一个类只需要负责一个职责,类的复杂度更低,更容易进行测试和调试。

  3. 降低代码的耦合度:当一个类只负责一个职责时,不同的职责可以分配到不同的类中,不同的类之间相互独立,从而降低了代码的耦合度。

  4. 提高代码的可复用性:一个类只负责一个职责,可以更容易地被其他模块复用。

  5. 便于扩展和维护:当需求变化时,如果每个类只负责一个职责,我们只需要修改相关的类即可,而不需要修改其他的类,从而更容易进行扩展和维护。

延伸面试题: 如何判断一个类的职责是否单一?

类中的代码行数、函数、或者属性过多;

函数或者属性过多,说明该类设计的不够单一,很可能包含其他职责内容

类依赖的其他类过多

说明该类有一些本该属于自己的功能,被抽取到其他类中了

私有方法过多

过多的私有方法,说明该类很可能包含其他类的职责内容

类中大量的方法都是集中操作类中的几个属性

说明属性设计不合理,本应该设计在其他类的属性

总结: 不同的应用场景、不同阶段的需求背景下,对同一个类的职责是否单一的判定,可能都是不一样的,最好的方式就是:

我们可以先写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候,我们就可以将这个粗粒度的类,拆分成几个更细粒度的类。这就是所谓的持续重构

2) 开放封闭原则

软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。也就是说,我们应该通过添加新的代码来扩展软件的功能,而不是修改已有的代码。

生活中的例子

开放封闭原则: 对扩展开发,对修改关闭. 我还是用汽车举例, 我是一个广东人, 今年冬天我要开车去东北 去东北吃雪, 因为广东人好吃嘛,没吃过雪 想去长白山吃点新鲜的雪. 东北雪很大 就需要给这个车的轮胎做防滑. 其实很简单只要给轮胎装上防滑链就可以了,这就属于是对轮胎的开放扩充. 在轮胎上面新增了防滑链,没有改变轮胎的原有功能. 我们不能因为为了给轮胎做防滑,而把汽车引擎换掉了.

代码举例: 还是计算面积的功能,现在我们又有一个计算三角形面积的需求,应该怎么做呢?

方式1: 直接修改 AreaCalculator

上面的做法就违反了开闭原则,因为假设后面又增加了 计算正方形、圆形等等的需求的时候,就需要不断的修改该类的代码,增加新的函数.

想要满足开闭原则,就必须要使用顶层设计思维,来解决问题

顶层设计思维

抽象意识

封装意识

扩展意识

比如在这里我们先利用抽象思维,将各类图形进行抽取,设计一个接口来表示图形(抽象),然后利用扩展思维,在接口中有一个抽象的方法,该方法的功能是获取面积.

每增加一种图形,就去实现该接口,然后重写计算面积方法即可.

有了抽象之后,就可以利用抽象,修改计算面积的方法,将参数改为接口类型,该程序的扩展性 就提升了,再有新的图形添加,也不需要修改计算程序的代码,只需添加新的类即可,从而实现了开闭原则.

在写代码的时候后,我们要多花点时间往前多思考一下,这段代码未来可能有哪些需求变更、如何设计代码结构,事先留好扩展点,以便在未来需求变更的时候,不需要改动代码整 体结构、做到最小代码改动的情况下,新的代码能够很灵活地插入到扩展点上,做到"对扩 展开放、对修改关闭"。

开放封闭原则的优点包括:

  1. 提高代码的可维护性和可复用性:开放封闭原则要求我们使用扩展来增加功能,而不是直接修改原有代码,这样可以避免对原有代码的破坏,从而提高代码的可维护性和可复用性。

  2. 降低代码的耦合度:当需要增加新的功能时,我们可以通过扩展来实现,而不是修改原有代码,这样可以避免不必要的代码耦合,从而提高代码的灵活性和可扩展性。

  3. 提高代码的稳定性:当需要增加新的功能时,我们只需要扩展已有的代码,而不需要修改原有的代码,这样可以避免引入新的错误,从而提高代码的稳定性。

  4. 提高代码的可测试性:使用开放封闭原则可以避免对原有代码的破坏,从而更容易进行测试和调试。

  5. 提高代码的可维护性:使用开放封闭原则可以使得代码更加模块化,更容易进行维护和修改。

3) 接口隔离原则

客户端不应该依赖它不需要的接口。也就是说,我们应该将不同的接口拆分成更小的、更具体的接口,从而避免客户端依赖于它们不需要的方法。这样可以降低接口的复杂性,提高系统的可维护性和可扩展性。

生活中的例子

接口隔离原则: 客户端不应该依赖它不需要的接口。 我们拿汽车的方向盘举例, 我们通过转动方向盘,可以控制车辆的转向,对于我们开车的人来说只需要知道如何转动方向盘就可以了 , 例如 倒车入库: 1、左后视镜下沿与停止线重合,向右打死方向盘;2、看右后视镜,车身库角30cm到了,向左回一圈方向盘;3、当车门把手与库角重合,方向盘右打死;4、看左侧后视镜,看到后边库角露出10cm,方向盘回正。

用户操作方向盘并不需要知道车辆的引擎、刹车、变速器等部件的具体实现细节,它只需要提供一个简单的接口 就是方向盘,即让驾驶员可以方便地控制车辆的方向。

下面的代码,就是没有遵守接口隔离原则,其中Rectangle类 实现了不应该它实现的方法.

接口隔离原则的优点包括:

提高代码的灵活性和可维护性:接口隔离原则要求我们定义精简的接口,这样可以使得代码更加灵活和可维护,因为我们只需要实现我们真正需要的接口即可。

  1. 提高代码的可测试性:接口隔离原则可以使得代码更加容易进行测试和调试,因为我们可以只测试我们真正需要的接口。

降低模块之间的耦合度:当模块之间只依赖于真正需要的接口时,它们之间的耦合度更低,更容易进行组合和修改。

  1. 提高代码的可复用性:当接口精简清晰时,代码的可复用性也会提高,因为我们可以更容易地将代码组合到不同的应用场景中。

  2. 提高代码的安全性:接口隔离原则可以避免一些意外的依赖关系,从而提高代码的安全性。

延伸面试题: 接口隔离原则与单一职责原则的区别

接口隔离原则和单一职责都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想,但两者是不同的:

单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。

单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。

4) 依赖倒置原则

高层模块不应该依赖于底层模块,而是应该依赖于抽象。也就是说,我们应该面向接口编程,而不是面向实现编程。

我们一起看一下下面代码有没有问题 ?

很显然上面的代码是有问题的, 我们应该遵守依赖倒置原则,高层模块不应该依赖于底层模块,而是应该依赖于抽象 .Car类是相对高层的,而QYEngine是底层的是具体实现.

依赖倒置原则的核心思想是:针对抽象编程,而不是针对具体实现编程。这样可以降低模块之间的耦合度,使系统更加灵活、可扩展和易于维护。同时,依赖倒置原则也可以促进面向对象设计的另一个原则------开闭原则的实现,即可以在不修改已有代码的情况下,通过添加新的实现来扩展系统的功能。

依赖倒置原则的优点包括:

  1. 提高代码的灵活性和可维护性:依赖倒置原则要求我们依赖于抽象而不是具体实现,这样可以使得代码更加灵活、可扩展和可维护。

  2. 降低模块之间的耦合度:当模块之间依赖于抽象而不是具体实现时,它们之间的耦合度更低,更容易进行组合和修改。

  3. 提高代码的可测试性:依赖倒置原则可以使得代码更加容易进行测试和调试,因为我们可以使用抽象来代替具体实现,从而更容易进行模拟和测试。

  4. 提高代码的可复用性:依赖倒置原则可以使得代码更加容易被复用,因为我们可以使用抽象来代替具体实现,从而更容易将代码组合到不同的应用场景中。

  5. 提高代码的可扩展性:依赖倒置原则可以使得代码更加容易进行扩展,因为我们可以通过添加新的实现类来扩展代码的功能,而不需要修改已有的代码。

延伸面试题: 关于依赖倒置原则、依赖注入、控制反转这三者之间的区别与联系

1 ) 依赖倒置原则

依赖倒置是一种通用的软件设计原则, 主要用来指导框架层面的设计。

高层模块不依赖低层模块,它们共同依赖同一个抽象。抽象不要依赖具体实现细节,具体实现细节依赖抽象。

2 ) 控制反转

控制反转与依赖倒置有一些相似, 它也是一种框架设计常用的模式,但并不是具体的方法。

"控制"指的是对程序执行流程的控制,而"反转"指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程通过框架来控制。流程的控制权从程序员"反转"给了框架。

Spring框架,核心模块IoC容器,就是通过控制反转这一种思想进行设计的

3 ) 依赖注入

依赖注入是实现控制反转的一个手段,它是一种具体的编码技巧。

我们不通过 new 的方式在类内部创建依赖的对象,而是将依赖的对象在外部创建好之后,通过构造函数等 方式传递(或注入)进来, 给类来使用。

依赖注入真正实现了面向接口编程的愿景,可以很方便地替换同一接口的不同实现,而不会影响到依赖这个接口的客户端。

5) 里氏替换原则

子类必须能够替换掉它们的父类。也就是说,在任何使用父类的地方,都应该能够使用子类来替代,而且程序不应该出现任何错误或异常

下面是一个 Rectangle 类,它是用表示矩形的,它有一个获取面积的方法

接下来再设计一个 Square 类继承自 Rectangle 类表示正方形,该类的构造方法中只接收一个边长即可,但是要将继承子父类的长和宽属性 都进行填充.

并且 在 Square 类中,我们重写了父类 Rectangle 中的 setWidth 和setHeight 方法,这样可以确保 Square 的长和宽始终相等

在测试方法中,我们创建了一个 Square 对象并将其赋值给一个 Rectangle 类型的变量 squareAsRectangle 。最后,我们调用了 printArea 方法来打印矩形和正方形的面积,可以看到程序输出正确的结果。

这个例子展示了里式替换原则的一个基本思想:子类应该能够替换掉父类并且不会影响程序的正确性。在这个例子中,我们可以将 Square 对象视为Rectangle 对象来使用,因为它们都有相同的方法和属性。这就是里式替换原则的应用。

里氏替换原则的优点包括:

  1. 提高代码的灵活性:通过遵循里氏替换原则,我们可以在不影响程序正确性的情况下,更加灵活地使用不同的子类对象来实现不同的功能。

  2. 提高代码的可扩展性:通过遵循里氏替换原则,我们可以更容易地向系统中添加新的子类,从而实现代码的扩展。

  3. 提高代码的可维护性:通过遵循里氏替换原则,我们可以更容易地修改子类的实现,从而提高代码的可维护性。

  4. 提高代码的可读性:通过遵循里氏替换原则,我们可以使得代码更加符合人类的思维方式,从而提高代码的可读性。

  5. 降低代码的耦合度:通过遵循里氏替换原则,我们可以降低代码的耦合度,使得各个模块之间的关系更加清晰,更容易进行组合和修改。

相关推荐
儿时可乖了13 分钟前
使用 Java 操作 SQLite 数据库
java·数据库·sqlite
ruleslol15 分钟前
java基础概念37:正则表达式2-爬虫
java
xmh-sxh-131431 分钟前
jdk各个版本介绍
java
XINGTECODE1 小时前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
天天扭码1 小时前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
程序猿进阶1 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺1 小时前
Spring Boot框架Starter组件整理
java·spring boot·后端
小曲程序1 小时前
vue3 封装request请求
java·前端·typescript·vue
凡人的AI工具箱1 小时前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang
陈王卜1 小时前
django+boostrap实现发布博客权限控制
java·前端·django