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

2024-02-28 07:20

本文主要是介绍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/754770

相关文章

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

MySQL常用字符串函数示例和场景介绍

《MySQL常用字符串函数示例和场景介绍》MySQL提供了丰富的字符串函数帮助我们高效地对字符串进行处理、转换和分析,本文我将全面且深入地介绍MySQL常用的字符串函数,并结合具体示例和场景,帮你熟练... 目录一、字符串函数概述1.1 字符串函数的作用1.2 字符串函数分类二、字符串长度与统计函数2.1

Mysql中设计数据表的过程解析

《Mysql中设计数据表的过程解析》数据库约束通过NOTNULL、UNIQUE、DEFAULT、主键和外键等规则保障数据完整性,自动校验数据,减少人工错误,提升数据一致性和业务逻辑严谨性,本文介绍My... 目录1.引言2.NOT NULL——制定某列不可以存储NULL值2.UNIQUE——保证某一列的每一

SQL Server 中的 WITH (NOLOCK) 示例详解

《SQLServer中的WITH(NOLOCK)示例详解》SQLServer中的WITH(NOLOCK)是一种表提示,等同于READUNCOMMITTED隔离级别,允许查询在不获取共享锁的情... 目录SQL Server 中的 WITH (NOLOCK) 详解一、WITH (NOLOCK) 的本质二、工作

MySQL CTE (Common Table Expressions)示例全解析

《MySQLCTE(CommonTableExpressions)示例全解析》MySQL8.0引入CTE,支持递归查询,可创建临时命名结果集,提升复杂查询的可读性与维护性,适用于层次结构数据处... 目录基本语法CTE 主要特点非递归 CTE简单 CTE 示例多 CTE 示例递归 CTE基本递归 CTE 结

Spring AI使用tool Calling和MCP的示例详解

《SpringAI使用toolCalling和MCP的示例详解》SpringAI1.0.0.M6引入ToolCalling与MCP协议,提升AI与工具交互的扩展性与标准化,支持信息检索、行动执行等... 目录深入探索 Spring AI聊天接口示例Function CallingMCPSTDIOSSE结束语

go动态限制并发数量的实现示例

《go动态限制并发数量的实现示例》本文主要介绍了Go并发控制方法,通过带缓冲通道和第三方库实现并发数量限制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录带有缓冲大小的通道使用第三方库其他控制并发的方法因为go从语言层面支持并发,所以面试百分百会问到

PyTorch中的词嵌入层(nn.Embedding)详解与实战应用示例

《PyTorch中的词嵌入层(nn.Embedding)详解与实战应用示例》词嵌入解决NLP维度灾难,捕捉语义关系,PyTorch的nn.Embedding模块提供灵活实现,支持参数配置、预训练及变长... 目录一、词嵌入(Word Embedding)简介为什么需要词嵌入?二、PyTorch中的nn.Em

Python Web框架Flask、Streamlit、FastAPI示例详解

《PythonWeb框架Flask、Streamlit、FastAPI示例详解》本文对比分析了Flask、Streamlit和FastAPI三大PythonWeb框架:Flask轻量灵活适合传统应用... 目录概述Flask详解Flask简介安装和基础配置核心概念路由和视图模板系统数据库集成实际示例Stre

Spring Bean初始化及@PostConstruc执行顺序示例详解

《SpringBean初始化及@PostConstruc执行顺序示例详解》本文给大家介绍SpringBean初始化及@PostConstruc执行顺序,本文通过实例代码给大家介绍的非常详细,对大家的... 目录1. Bean初始化执行顺序2. 成员变量初始化顺序2.1 普通Java类(非Spring环境)(