【设计模式】SOLID设计原则

2024-04-14 20:28

本文主要是介绍【设计模式】SOLID设计原则,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1、什么是SOLID设计原则

SOLID 是面向对象设计中的五个基本设计原则的首字母缩写,它们是:

单一职责原则(Single Responsibility Principle,SRP)
类应该只有一个单一的职责,即一个类应该有且只有一个改变的理由。这意味着一个类应该只负责一个特定的功能或任务,而不是多个不相关的功能。这样做可以提高类的内聚性,并使得类更容易理解、修改和测试。

开放-封闭原则(Open/Closed Principle,OCP)
软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着在不修改现有代码的情况下,应该能够通过添加新的代码来扩展系统的功能。这样做可以使得系统更加稳定,减少修改现有代码可能带来的风险。

里氏替换原则(Liskov Substitution Principle,LSP)
子类型必须能够替换其基类型。换句话说,任何可以接受基类型的地方都可以接受子类型,而且不会引发意外的行为。这样做可以保持系统的一致性和可靠性,并且确保使用继承时不会破坏代码的正确性。

接口隔离原则(Interface Segregation Principle,ISP)
客户端不应该被迫依赖于其不使用的接口。这意味着应该将接口设计成小而专注的接口,而不是大而臃肿的接口。这样做可以降低耦合性,并且使得系统更加灵活和易于维护。

依赖倒置原则(Dependency Inversion Principle,DIP)
高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。这样做可以降低模块之间的耦合度,并且使得系统更易于扩展和修改。

这些原则是由罗伯特·马丁(Robert C. Martin)等人在面向对象设计中提出的,它们提供了一套指导原则,帮助设计出高质量、可维护和可扩展的面向对象系统。

2、单一职责原则

单一职责原则(Single Responsibility Principle,SRP)要求一个类或模块应该只有一个单一的责任,即一个类或模块应该只负责一个特定的功能或任务。这样做可以提高代码的内聚性、可维护性和可测试性。

让我们通过一个简单的例子来说明单一职责原则:

假设我们有一个简单的应用程序,用于处理用户信息,包括保存用户信息到数据库和从数据库中检索用户信息。我们可以将这个功能拆分成两个类:一个负责保存用户信息,一个负责检索用户信息。

#include <iostream>
#include <string>// 负责保存用户信息到数据库
class UserSaver {
public:void saveUser(const std::string& username, const std::string& email) {// 将用户信息保存到数据库std::cout << "用户信息已保存到数据库:" << username << ", " << email << std::endl;}
};// 负责从数据库中检索用户信息
class UserRetriever {
public:void retrieveUser(const std::string& username) {// 从数据库中检索用户信息std::cout << "从数据库中检索到用户信息:" << username << std::endl;}
};int main() {UserSaver userSaver;userSaver.saveUser("Alice", "alice@example.com");UserRetriever userRetriever;userRetriever.retrieveUser("Alice");return 0;
}

在这个例子中,我们有两个类 UserSaverUserRetriever,它们分别负责保存用户信息和检索用户信息。这两个类各自都只有一个单一的职责,即负责一个特定的功能。如果我们需要修改保存用户信息的逻辑,我们只需要修改 UserSaver 类;如果我们需要修改检索用户信息的逻辑,我们只需要修改 UserRetriever 类。这样做提高了代码的可维护性,并且使得每个类更加简单和易于理解。

3、开放-封闭原则

开放-封闭原则(Open/Closed Principle,OCP)是面向对象设计中的一个基本原则,由柏拉图·梅特克斯(Bertrand Meyer)在他的《面向对象软件构造》(Object-Oriented Software Construction)一书中首次提出。它的核心思想是软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。换句话说,软件实体在不修改现有代码的情况下,应该能够通过添加新的代码来扩展系统的功能。

开放-封闭原则的目的是为了提高系统的可维护性、可扩展性和稳定性。通过遵循这一原则,可以使得系统更容易理解和修改,并且减少对现有代码的影响。

在实际应用中,可以通过以下几种方式来遵循开放-封闭原则:

抽象化:通过使用抽象类、接口或者抽象函数来定义可扩展的接口,从而使得系统可以根据需要进行扩展,而不必修改现有代码。

多态性:利用多态性和继承机制,使得系统可以通过添加新的子类来扩展功能,而不必修改基类或现有代码。

组合/聚合:通过组合或聚合关系来构建对象之间的关联关系,从而使得系统可以通过添加新的组件来扩展功能,而不必修改现有组件。

模块化:将系统分解成独立的模块或组件,使得每个模块只负责一个特定的功能,从而使得系统可以通过添加新的模块来扩展功能,而不必修改现有模块。

总之,开放-封闭原则指导我们设计出易于扩展和维护的软件系统,通过封装变化和利用多态性,使得系统可以根据需要进行扩展,而不必修改现有代码。

让我们通过一个简单的例子来说明开放-封闭原则。

假设我们有一个简单的图形绘制程序,它可以绘制不同形状的图形,包括圆形和矩形。现在我们希望在程序中添加新的图形类型,比如三角形。我们可以通过遵循开放-封闭原则来扩展程序的功能,而不必修改现有的代码。

首先,我们定义一个抽象基类 Shape,它有一个纯虚函数 draw 用于绘制图形:

#include <iostream>// 抽象基类:图形
class Shape {
public:virtual void draw() const = 0;
};

然后,我们定义具体的图形类,比如 CircleRectangle 类,它们分别继承自 Shape 类并实现 draw 函数:

// 圆形类
class Circle : public Shape {
public:void draw() const override {std::cout << "绘制圆形\n";}
};// 矩形类
class Rectangle : public Shape {
public:void draw() const override {std::cout << "绘制矩形\n";}
};

现在,如果我们想要添加新的图形类型,比如三角形,我们只需要添加一个新的类 Triangle,它也继承自 Shape 类并实现 draw 函数:

// 三角形类
class Triangle : public Shape {
public:void draw() const override {std::cout << "绘制三角形\n";}
};

通过这种方式,我们可以在不修改现有代码的情况下,通过添加新的类来扩展程序的功能,符合开放-封闭原则。这样做提高了代码的可维护性和可扩展性,使得系统更易于理解和修改。

4、 里氏替换原则

里氏替换原则(Liskov Substitution Principle,LSP)是面向对象设计中的一个基本原则,由芭芭拉·利斯科夫(Barbara Liskov)在 1987 年提出。该原则指出,子类型必须能够替换其基类型,即任何可以接受基类型的地方都可以接受子类型,而且不会引发意外的行为。

在更通俗的说法中,如果一个类型是子类型(派生类),那么它应该可以替换掉基类型(基类)并且不会破坏程序的正确性。换句话说,子类型应该保持基类型的行为,而不是产生意外的行为。

遵循里氏替换原则的目的是为了确保代码的一致性和可靠性,使得系统更易于理解、扩展和维护。如果违反了里氏替换原则,那么可能会导致程序的错误行为和不稳定性。

在实际应用中,可以通过以下几点来遵循里氏替换原则:

子类型必须实现基类型的所有行为,不能减少基类型的约束条件。
子类型可以增加新的行为,但不能修改基类型已有的行为。
子类型的前置条件(即输入条件)必须比基类型更宽松。
子类型的后置条件(即输出条件)必须比基类型更严格。

通过遵循里氏替换原则,可以确保系统的稳定性和可靠性,并且使得系统更易于扩展和维护。

让我们通过一个简单的例子来说明里氏替换原则。

假设我们有一个简单的几何图形类层次结构,包括基类 Shape 和两个子类 RectangleSquare,其中 SquareRectangle 的子类。

现在让我们来看看是否满足里氏替换原则:

#include <iostream>// 基类:图形
class Shape {
public:virtual void draw() const {std::cout << "绘制图形\n";}
};// 矩形类
class Rectangle : public Shape {
public:void draw() const override {std::cout << "绘制矩形\n";}
};// 正方形类
class Square : public Rectangle {
public:void draw() const override {std::cout << "绘制正方形\n";}
};// 绘制图形函数
void drawShape(const Shape& shape) {shape.draw();
}int main() {Rectangle rectangle;Square square;drawShape(rectangle); // 绘制矩形drawShape(square);    // 绘制正方形return 0;
}

在这个例子中,我们有一个基类 Shape,它有一个 draw 方法用于绘制图形。然后,我们有一个 Rectangle 类和一个 Square 类,它们分别继承自 Shape 类,并且都重写了 draw 方法以实现各自特定的绘制行为。

main 函数中,我们创建了一个 Rectangle 对象和一个 Square 对象,并且分别调用了 drawShape 函数来绘制这些图形。

在这个例子中,Square 类是 Rectangle 类的子类,符合继承关系。而且,在 drawShape 函数中,我们可以接受 Shape 类型的参数,并且传入 RectangleSquare 对象进行绘制,而不会产生意外的行为。

因此,这个例子满足了里氏替换原则:子类型(Square)可以替换其基类型(Rectangle)而不会引发意外的行为,程序的行为保持一致。

5、接口隔离原则

接口隔离原则(Interface Segregation Principle,ISP)是面向对象设计中的一个基本原则,由罗伯特·马丁(Robert C. Martin)在他的《敏捷软件开发:原则、模式和实践》(Agile Software Development, Principles, Patterns, and Practices)一书中提出。接口隔离原则指出,客户端不应该被迫依赖于其不使用的接口。换句话说,一个类不应该依赖于它不需要使用的接口,应该将接口设计成小而专注的接口,而不是大而臃肿的接口。

接口隔离原则的目的是为了提高系统的灵活性和可维护性。通过将接口拆分成小而专注的接口,可以降低类之间的耦合度,使得系统更易于理解、扩展和修改。同时,这也可以避免因为接口的臃肿而导致的功能耦合和代码冗余。

在实践中,可以通过以下几点来遵循接口隔离原则:

  1. 将大而臃肿的接口拆分成多个小而专注的接口,每个接口只包含一个单一的功能或职责。
  2. 只在需要使用某个接口的地方引入该接口,避免将不需要的接口强加给客户端。
  3. 根据客户端的需求,设计出合适的接口,并且保持接口的稳定性,避免频繁地修改接口。

通过遵循接口隔离原则,可以使得系统更灵活、更易于维护,并且能够更好地应对需求变化。

让我们通过一个简单的例子来说明接口隔离原则。

假设我们有一个简单的文件操作接口 FileOperation,它定义了一些文件操作的方法,比如打开文件、读取文件和关闭文件等。然后,我们有两个类 TextEditorImageEditor,它们分别实现了这个接口。

首先,让我们定义文件操作接口 FileOperation

#include <iostream>// 文件操作接口
class FileOperation {
public:virtual void open() = 0;virtual void read() = 0;virtual void close() = 0;
};

然后,我们有一个文本编辑器 TextEditor,它需要实现文件操作接口来打开和读取文本文件:

// 文本编辑器类
class TextEditor : public FileOperation {
public:void open() override {std::cout << "打开文本文件\n";}void read() override {std::cout << "读取文本文件\n";}void close() override {std::cout << "关闭文本文件\n";}
};

接着,我们有一个图像编辑器 ImageEditor,它也需要实现文件操作接口来打开和读取图像文件:

// 图像编辑器类
class ImageEditor : public FileOperation {
public:void open() override {std::cout << "打开图像文件\n";}void read() override {std::cout << "读取图像文件\n";}void close() override {std::cout << "关闭图像文件\n";}
};

在这个例子中,TextEditorImageEditor 都实现了 FileOperation 接口,但是它们只使用了其中的一部分方法(即打开和读取文件)。如果我们将所有文件操作都放在一个大的接口中,那么 TextEditorImageEditor 就不得不实现它们不需要的方法,违反了接口隔离原则。

通过将接口设计成小而专注的接口,每个接口只包含一个单一的功能或职责,我们遵循了接口隔离原则,并且使得系统更易于理解、扩展和修改。

6、依赖倒置原则

依赖倒置原则(Dependency Inversion Principle,DIP)是面向对象设计中的一个基本原则,由罗伯特·马丁(Robert C. Martin)在他的《敏捷软件开发:原则、模式和实践》(Agile Software Development, Principles, Patterns, and Practices)一书中提出。依赖倒置原则指出,高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。

依赖倒置原则的核心思想是通过使用抽象来降低类之间的耦合度,从而使得系统更加灵活、可扩展和易于维护。具体来说,依赖倒置原则要求我们将程序的设计重心放在抽象上,而不是具体实现上,通过使用接口、抽象类或者依赖注入等方式来实现依赖倒置。

在实践中,可以通过以下几点来遵循依赖倒置原则:

  1. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象。即高层模块和低层模块都应该依赖于同一个抽象接口或抽象类。
  2. 抽象不应该依赖于具体实现,具体实现应该依赖于抽象。即抽象接口或抽象类不应该依赖于具体实现,而是具体实现应该依赖于抽象接口或抽象类。
  3. 可以通过依赖注入(Dependency Injection)等方式来实现依赖倒置,将具体实现的创建和注入交给外部,而不是在类内部创建具体实现的对象。

通过遵循依赖倒置原则,可以使得系统更加灵活、可扩展和易于维护,减少类之间的耦合度,提高代码的可复用性和可测试性。

让我们通过一个简单的例子来说明依赖倒置原则。

假设我们有一个简单的电子邮件发送系统,其中包含一个 EmailSender 类用于发送电子邮件。一开始,EmailSender 类直接依赖于具体的邮件服务提供商,比如 Gmail。这个设计违反了依赖倒置原则,因为高层模块 EmailSender 直接依赖于低层模块,即具体的邮件服务提供商。

#include <iostream>// 具体的邮件服务提供商:Gmail
class Gmail {
public:void sendEmail(const std::string& recipient, const std::string& message) {std::cout << "Sending email to " << recipient << " via Gmail: " << message << std::endl;}
};// 邮件发送类
class EmailSender {
private:Gmail gmail;public:void sendEmail(const std::string& recipient, const std::string& message) {gmail.sendEmail(recipient, message);}
};int main() {EmailSender sender;sender.sendEmail("example@example.com", "Hello, this is a test email.");return 0;
}

现在,让我们通过引入抽象来遵循依赖倒置原则。我们可以定义一个抽象的邮件服务接口 EmailService,并让 Gmail 类实现这个接口。然后,EmailSender 类只依赖于 EmailService 接口,而不是具体的邮件服务提供商。

#include <iostream>// 抽象的邮件服务接口
class EmailService {
public:virtual void sendEmail(const std::string& recipient, const std::string& message) = 0;
};// 具体的邮件服务提供商:Gmail
class Gmail : public EmailService {
public:void sendEmail(const std::string& recipient, const std::string& message) override {std::cout << "Sending email to " << recipient << " via Gmail: " << message << std::endl;}
};// 邮件发送类
class EmailSender {
private:EmailService* emailService;public:EmailSender(EmailService* service) : emailService(service) {}void sendEmail(const std::string& recipient, const std::string& message) {emailService->sendEmail(recipient, message);}
};int main() {Gmail gmail;EmailSender sender(&gmail);sender.sendEmail("example@example.com", "Hello, this is a test email.");return 0;
}

通过这种方式,EmailSender 类不再直接依赖于具体的邮件服务提供商,而是依赖于抽象的邮件服务接口 EmailService。这样做符合依赖倒置原则,使得系统更加灵活、可扩展和易于维护,因为现在可以轻松地切换不同的邮件服务提供商,而不需要修改 EmailSender 类的代码。

这篇关于【设计模式】SOLID设计原则的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

怎么让1台电脑共享给7人同时流畅设计

在当今的创意设计与数字内容生产领域,图形工作站以其强大的计算能力、专业的图形处理能力和稳定的系统性能,成为了众多设计师、动画师、视频编辑师等创意工作者的必备工具。 设计团队面临资源有限,比如只有一台高性能电脑时,如何高效地让七人同时流畅地进行设计工作,便成为了一个亟待解决的问题。 一、硬件升级与配置 1.高性能处理器(CPU):选择多核、高线程的处理器,例如Intel的至强系列或AMD的Ry

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机

SprinBoot+Vue网络商城海鲜市场的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍:CSDN认证博客专家,CSDN平台Java领域优质创作者,全网30w+

JVM内存调优原则及几种JVM内存调优方法

JVM内存调优原则及几种JVM内存调优方法 1、堆大小设置。 2、回收器选择。   1、在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。   2、对JVM内存的系统级的调优主要的目的是减少

单片机毕业设计基于单片机的智能门禁系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍程序代码部分参考 设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订

Spring的设计⽬标——《Spring技术内幕》

读《Spring技术内幕》第二版,计文柯著。 如果我们要简要地描述Spring的设计⽬标,可以这么说,Spring为开发者提供的是⼀个⼀站式的轻量级应⽤开发框架(平台)。 作为平台,Spring抽象了我们在 许多应⽤开发中遇到的共性问题;同时,作为⼀个轻量级的应⽤开发框架,Spring和传统的J2EE开发相⽐,有其⾃⾝的特点。 通过这些⾃⾝的特点,Spring充分体现了它的设计理念:在

开题报告中的研究方法设计:AI能帮你做什么?

AIPaperGPT,论文写作神器~ https://www.aipapergpt.com/ 大家都准备开题报告了吗?研究方法部分是不是已经让你头疼到抓狂? 别急,这可是大多数人都会遇到的难题!尤其是研究方法设计这一块,选定性还是定量,怎么搞才能符合老师的要求? 每次到这儿,头脑一片空白。 好消息是,现在AI工具火得一塌糊涂,比如ChatGPT,居然能帮你在研究方法这块儿上出点主意。是不

创业者该如何设计公司的股权架构

本文来自七八点联合IT橘子和车库咖啡的一系列关于设计公司股权结构的讲座。 主讲人何德文: 在公司发展的不同阶段,创业者都会面临公司股权架构设计问题: 1.合伙人合伙创业第一天,就会面临股权架构设计问题(合伙人股权设计); 2.公司早期要引入天使资金,会面临股权架构设计问题(天使融资); 3.公司有三五十号人,要激励中层管理与重要技术人员和公司长期走下去,会面临股权架构设计问题(员工股权激