SOLID 设计原则,代码示例(show me the code)

2024-03-28 05:08

本文主要是介绍SOLID 设计原则,代码示例(show me the code),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

认识 SOLID 原则

无论是软件系统设计,还是代码实现,遵循有效和明确的设计原则,都利于系统软件灵活可靠,安全快速的落地,更重要的是能灵活地应对需求,简化系统扩展和维护,避免无效的加班。本文主要讨论面向对象软件开发中最流行的设计原则- SOLID,它是五个设计原则为了方便记忆而组成的首字母缩写:

  • 单一职责原则
  • 开放/封闭原则
  • 里式替换原则
  • 接口隔离原则
  • 依赖倒置原则

 

S:单一职责原则 (SRP)

 

基本概念

单一职责原则 (SRP) 英文全称为 Single Responsibility Principle,是最简单,但也是最难用好的原则之一。它的定义也很简单:对于一个类而言,应该仅有一个引起它变化的原因。其中变化的原因就表示了这个类的职责,它可能是某个特定领域的功能,可能是某个需求的解决方案。


这个原则表达的是不要让一个类承担过多的责任,一旦有了多个职责,那么它就越容易因为某个职责而被更改,这样的状态是不稳定的,不经意的修改很有可能影响到这个类的其他功能。因此,我们需要将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,不同类之间的变化互不影响。

 

实例说明

举一个具体的例子,有一个用于实现编辑和打印报表的类,这样的类存在两个变化的原因:第一,报表的内容可以改变(编辑)。第二,报表的格式可以改变(打印)。如果有一个对于报表编辑流程的修改,而报表的编辑流程会导致公共状态或者依赖关系的改变,使得打印功能的代码无法工作。所以单一职责原则认为这两个变化的原因事实上是两个分离的功能,它们应该分离在不同的类中。
image.png

 

相关设计模式

面对违背单一职责原则的程序代码,我们可以利用外观模式,代理模式,桥接模式,适配器模式,命令模式对已有设计进行重构,实现多职责的分离。

 

小结

单一职责原则用于控制类的粒度大小,减少类中不相关功能的代码耦合,使得类更加的健壮;另外,单一职责原则也适用于模块之间解耦,对于模块的功能划分有很大的指导意义。

 

O:开闭原则 (OCP)

 

基本概念

开闭原则 (OCP) 英文全称为 Open-Closed Principle,基本定义是软件中的对象(类,模块,函数等)应该对于扩展是开放的,但是对于修改是封闭的。这里的对扩展开放表示这添加新的代码,就可以让程序行为扩展来满足需求的变化;对修改封闭表示在扩展程序行为时不要修改已有的代码,进而避免影响原有的功能。


要实现不改代码的情况下,仍要去改变系统行为的关键就是抽象和多态,通过接口或者抽象类定义系统的抽象层,再通过具体类来进行扩展。这样一来,无须对抽象层进行任何改动,只需要增加新的具体类来实现新的业务功能即可,达到开闭原则的要求。

 

实例说明

同样,举个例子来更深刻地理解开闭原则:有一个用于图表显示的 Display 类,它能绘制各种类型的图表,比如饼状图,柱状图等;而需要绘制特定图表时,都强依赖了对应类型的图表,Display 类的内部实现如下:

public void display(String type) {if (type.equals("pie")) {  PieChart chart = new PieChart();  chart.display();  }  else if (type.equals("bar")) {  BarChart chart = new BarChart();  chart.display();  } 
}

基于上述的代码,如果需要新增一个图表,比如折线图 LineChart ,就要修改 Display 类的 display() 方法,增加新增的判断逻辑,很显然这样的做法违反开闭原则。而让类的实现符合开闭原则的方式就是引入抽象图表类 AbstractChart,作为其他图表的基类,让 Display 依赖这个抽象图表类 AbstractChart,然后通过 Display 决定使用哪种具体的图表类,实现代码变成了这样:

private Abstractchart chart;public void display() {chart.display();  
}

现在我们需要新增折线图显示,在客户端向 Display 中注入一个 LineChart 对象即可,无须修改现有类库的源代码。
image.png

 

相关设计模式

面对违背开闭原则的程序代码,可以用到的设计模式有很多,比如工厂模式,观察者模式,模板方法模式,策略模式,组合模式,使用相关设计模式的关键点就是识别出最有可能变化和扩展的部分,然后构造抽象来隔离这些变化。

 

小结

有了开闭原则,面向需求的变化就能进行快速的调整实现功能,这大大提高系统的灵活性,可重用性和可维护性,但会增加一定的复杂性。

 

L: 里式替换原则 (LSP)

 

基本概念

里式替换原则 (LSP) 英文全称为 Liskov Substitution Principle,基本定义为:在不影响程序正确性的基础上,所有使用基类的地方都能使用其子类的对象来替换。这里提到的基类和子类说的就是具有继承关系的两类对象,当我们传递一个子类型对象时,需要保证程序不会改变任何原基类的行为和状态,程序能正常运作。

 

实例说明

为了能理解里式替换原则,这里举一个经典的违反里式替换原则的例子:正方形/长方形问题。
image.png
上图为正方形/长方形问题的类层次结构,Square 类继承了 Rectangle 类,但是 Rectangle 类的宽高可以分别修改,但是 Suqare 类的宽高则必须一同修改。如果 User 类操作 Rectangle 类时,但实际对象是 Suqare 类型时,就会造成程序的出错,如下方代码:

Rectangle r = ...; // 返回具体类型对象
r.setWidth(5);
r.setHeight(2);
assert(r.area() == 10);

当返回具体类型对象为 Suqare 类型,面积为 10 的断言就是失败,这样明显是不符合里式替换原则的。

 

小结

要让程序代码符合里式替换原则,需要保证子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法,换句话就是子类可以扩展父类的功能,但不能改变父类原有的功能。


另一方面,里式替换原则也是对开闭原则的补充,不仅适用于继承关系,还适用于实现关系的设计,常提到的 IS-A 关系是针对行为方式来说的,如果两个类的行为方式是不相容,那么就不应该使用继承,更好的方式是提取公共部分的方法来代替继承。

 

I:接口隔离原则 (ISP)

 

基本概念

接口隔离原则 (ISP) 英文全称为 Interface Segregation Principle,基本定义:客户端不应该依赖那些它不需要的接口。客户端应该只依赖它实际使用的方法,因为如果一个接口具备了若干个方法,那就意味着它的实现类都要实现所有接口方法,从代码结构上就十分臃肿。

 

实例说明


image.png


现在我们看下一个违反接口隔离原则的例子,从上面类结构图中,有多个用户需要操作 Operation 类。如果 User1 只需要使用 operation1 方法,User2 只需要使用 operation2 方法,User3 只需要使用 operation3 方法,那么很明显对于 User1 来说,不应该看到 operation2 和 operation3 这两个方法,要减少对自己不关心的方法的依赖,防止 Operation 类中 operation2 和 operation3 方法的修改,影响到 User1 的功能。这个问题可以通过将不同的操作隔离成独立的接口来解决,具体如下图所示。
image.png






基于接口隔离原则,我们需要做的就是减少定义大而全的接口,类所要实现的接口应该分解成多个接口,然后根据所需要的功能去实现,并且在使用到接口方法的地方,用对应的接口类型去声明,这样可以解除调用方与对象非相关方法的依赖关系。总结一下,接口隔离原则主要功能就是控制接口的粒度大小,防止暴露给客户端无相关的代码和方法,保证了接口的高内聚,降低与客户端的耦合。

 

D:依赖倒置原则 (DIP)

 

基本概念

依赖倒置原则 (DIP) 英文全称 Dependency Inversion Principle, DIP),基本定义是:

  • 高层模块不应该依赖低层模块,应该共同依赖抽象;
  • 抽象不应该依赖细节,细节应该依赖抽象。

这里的抽象就是接口和抽象类,而细节就是实现接口或继承抽象类而产生的类。

 

实例说明

如何理解“高层模块不应该依赖低层模块,应该共同依赖抽象”呢?如果高层模块依赖于低层模块,那么低层模块的改动很有可能影响到高层模块,从而导致高层模块被迫改动,这样一来让高层模块的重用变得非常困难。
file而最佳的做法就如上图一样,在高层模块构建一个稳定的抽象层,并且只依赖这个抽象层;而由底层模块完成抽象层的实现细节。这样一来,高层类都通过该抽象接口使用下一层,移除了高层对底层实现细节的依赖。

 

相关设计模式

关于依赖倒置原则,可以用到的设计模式有工厂模式,模板方法模式,策略模式。

 

小结

依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。同时依赖倒置原则也是框架设计的核心原则,善于创建可重用的框架和富有扩展性的代码,比如 Tomcat 容器的 Servlet 规范实现,Spring Ioc 容器实现。

 

结语

到这里,SOLID 设计原则就全部介绍完了,本文的主要目的还是对这六项原则系统地整理和总结,在后续的程序设计开发过程中能有意识地识别出设计原则和模式。如果大家对设计原则有更多想法和理解,欢迎留言,大家共同探讨。

这篇关于SOLID 设计原则,代码示例(show me the code)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/854525

相关文章

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函

Python中logging模块用法示例总结

《Python中logging模块用法示例总结》在Python中logging模块是一个强大的日志记录工具,它允许用户将程序运行期间产生的日志信息输出到控制台或者写入到文件中,:本文主要介绍Pyt... 目录前言一. 基本使用1. 五种日志等级2.  设置报告等级3. 自定义格式4. C语言风格的格式化方法

Spring 中的切面与事务结合使用完整示例

《Spring中的切面与事务结合使用完整示例》本文给大家介绍Spring中的切面与事务结合使用完整示例,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录 一、前置知识:Spring AOP 与 事务的关系 事务本质上就是一个“切面”二、核心组件三、完

sky-take-out项目中Redis的使用示例详解

《sky-take-out项目中Redis的使用示例详解》SpringCache是Spring的缓存抽象层,通过注解简化缓存管理,支持Redis等提供者,适用于方法结果缓存、更新和删除操作,但无法实现... 目录Spring Cache主要特性核心注解1.@Cacheable2.@CachePut3.@Ca

QT Creator配置Kit的实现示例

《QTCreator配置Kit的实现示例》本文主要介绍了使用Qt5.12.12与VS2022时,因MSVC编译器版本不匹配及WindowsSDK缺失导致配置错误的问题解决,感兴趣的可以了解一下... 目录0、背景:qt5.12.12+vs2022一、症状:二、原因:(可以跳过,直奔后面的解决方法)三、解决方

MySQL中On duplicate key update的实现示例

《MySQL中Onduplicatekeyupdate的实现示例》ONDUPLICATEKEYUPDATE是一种MySQL的语法,它在插入新数据时,如果遇到唯一键冲突,则会执行更新操作,而不是抛... 目录1/ ON DUPLICATE KEY UPDATE的简介2/ ON DUPLICATE KEY UP

Python中Json和其他类型相互转换的实现示例

《Python中Json和其他类型相互转换的实现示例》本文介绍了在Python中使用json模块实现json数据与dict、object之间的高效转换,包括loads(),load(),dumps()... 项目中经常会用到json格式转为object对象、dict字典格式等。在此做个记录,方便后续用到该方

MySQL分库分表的实践示例

《MySQL分库分表的实践示例》MySQL分库分表适用于数据量大或并发压力高的场景,核心技术包括水平/垂直分片和分库,需应对分布式事务、跨库查询等挑战,通过中间件和解决方案实现,最佳实践为合理策略、备... 目录一、分库分表的触发条件1.1 数据量阈值1.2 并发压力二、分库分表的核心技术模块2.1 水平分

SpringBoot请求参数传递与接收示例详解

《SpringBoot请求参数传递与接收示例详解》本文给大家介绍SpringBoot请求参数传递与接收示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋... 目录I. 基础参数传递i.查询参数(Query Parameters)ii.路径参数(Path Va