【设计模式】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 类的代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/816057.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

js和ES的关系

ES和JS之间的关系是&#xff1a;ES&#xff08;ECMAScript&#xff09;是JS&#xff08;JavaScript&#xff09;的一个规范或者标准&#xff0c;而JS则是ES的实现。具体来说&#xff0c;JavaScript 是一种在浏览器中运行的脚本语言&#xff0c;用于实现网页的交互功能。而 ECMA…

力扣面试150 分发糖果 分步贪心

Problem: 135. 分发糖果 思路 &#x1f468;‍&#x1f3eb; 参考&#xff1a;代码随想录 一次是从左到右遍历&#xff0c;只比较右边孩子评分比左边大的情况。一次是从右到左遍历&#xff0c;只比较左边孩子评分比右边大的情况。 复杂度 时间复杂度: O ( n ) O(n) O(n) …

低成本,高效能:探索物联网新宠LoRa

LoRa是什么&#xff1f; LoRa是一种物联网无线传输技术&#xff0c;利用调制解调器实现低功耗远距离数据传输。其基本工作原理是通过基站发送数据到特定终端设备&#xff0c;实现双向数据传输。 LoRa无线传输技术是一种为低功耗和低成本设计的无线技术&#xff0c;用于实现远距…

【Linux】CentOS 7安装后没有图形界面

专栏文章索引&#xff1a;Linux 有问题可私聊&#xff1a;QQ&#xff1a;3375119339 目录 一、项目场景 二、问题描述 三、原因分析 四、解决方案 1.当前处于命令行界面&#xff0c;可以切换为图形界面 2.安装时没有安装图形界面&#xff0c;选择了Minimal Install 3.下…

鸿蒙端云一体化开发--开发云函数--适合小白体制

开发云函数 那什么是云函数&#xff1f;我们将来又怎么去使用这个云函数呢&#xff1f; 答&#xff1a;我们之前要编写一些服务端的业务逻辑代码&#xff0c;那现在&#xff0c;在这种端云一体化的开发模式下&#xff0c;我们是把服务端的业务逻辑代码&#xff0c;通过云函数来…

linux安装和使用-第一天

一. 安装linux系统 安装过程:略注意事项: 安装时一定一定一定不要选择有中文的目录包括镜像文件所在的目录,否则会发生各种问题,比如VMware Tools是灰色的.1. 安装ssh工具 (1) 安装命令 # 第一次安装系统需要更新一下apt的源,他维护了软件依赖关系,否则安装不了软件,每次安装…

网络安全教程及案例分析

一、网络安全教程 &#xff08;一&#xff09;网络安全基础知识 计算机基础知识&#xff1a;了解计算机的硬件、软件、操作系统和网络结构&#xff0c;有助于我们更好地理解网络安全的概念和技术。这些基础知识为我们提供了对计算机系统的全面认识&#xff0c;从而能够更准确…

【云计算】混合云概述

混合云概述 1.混合云定义2.云混合的形态2.1 公有云之间的混合2.2 私有云之间的混合2.3 公有云和私有云的混合2.4 公有云和传统IT的混合 3.小结 混合云 是近几年来被经常提及的一个新的云架构体系&#xff0c;根据 NIST&#xff08;美国国家标准与技术研究院&#xff09;的定义&…

MT3020 任务分配

思路&#xff1a;利用二分找到某个时间是满足“k个人可以完成” &#xff0c;并且时间最小。 因为尽量让后面的人做任务&#xff0c;所以从后往前排任务&#xff08;倒着分配&#xff09;。从后往前遍历任务&#xff0c;如果此人加上这个任务超出之前求得的时间&#xff0c;就…

Csapp整数浮点数操作实验(精讲)

a. int conditional(int x, int y, int z) 功能&#xff1a;实现与三目运算符表达式 x ? y : z 具有等价功能的函数合法的运算符&#xff1a;! ~ & ^ | << >>可使用的运算符数&#xff1a;16难度&#xff1a;4寻找一种转换&#xff0c;使得当x非0时转变为0x…

快速入门深度学习9.1(用时20min)——GRU

速通《动手学深度学习》9.1 写在最前面九、现代循环神经网络9.1 门控循环单元&#xff08;GRU&#xff09;9.1.1. 门控隐状态9.1.1.1. 重置门和更新门9.1.1.2. 候选隐状态9.1.1.3. 隐状态 9.1.3 API简洁实现小结 &#x1f308;你好呀&#xff01;我是 是Yu欸 &#x1f30c; 20…

空指针与野指针的辨析

空指针 空指针不指向任何实际的对象或者函数&#xff0c;反过来&#xff0c;任何的对象或者函数也不可能是空指针。 在程序中得到空指针的办法就是使用预定义的NULL&#xff0c; int *ip NULL; 校验一个指针是否为空指针可以用 if (ip NULL) NULL是标准规定的宏定义&am…

h5 笔记4 表格与表单

<table></table>设置表格&#xff1b; <tr></tr>设置行数&#xff1b; <td></td>设置列数&#xff1b; <caption></caption>设置表格标题&#xff1b; <th></th>设置列标题。 直列&#xff1a;column&#xf…

独孤思维:完美的赚钱人设,一定是假的

01 做个人ip&#xff0c;设立自己的人设。 不要完美无缺。 完美100%是假的。 都是人&#xff0c;谁没个缺点。 不要把自己架得太高&#xff0c;搞得事事完美。 这是不合理的。 粉丝看了&#xff0c;会觉得很假。 一定要真实。 哪怕你有这样的缺点&#xff0c;那样的毛…

Pytest精通指南(09)利用Fixture给函数设置别名

文章目录 前言测试用例默认显示传递一个参数传递多个参数 利用Fixture修改测试函数名称传递一个参数传递多个参数 验证ids和params长度不一致修改Fixture函数名称 前言 在 pytest 中&#xff0c;pytest.fixture 装饰器用于定义可以在多个测试函数中重用的设置和清理代码。 name…

android gradle 配置远程仓库

build.gradle buildscript { ext.kotlin_version "1.6.0" // 使用适合你项目的Kotlin版本 repositories { maven { url http://maven.aliyun.com/nexus/content/groups/public/ } maven { url http://maven.aliyun.com/nexus/content/repos…

虚拟机下CentOS7开启SSH连接

虚拟机下CentOS7开启SSH连接 自己在VMware中装了CentOS 6.3&#xff0c;然后主机&#xff08;或者说xshell&#xff09;与里面的虚拟机连不通&#xff0c;刚学习&#xff0c;一头雾水&#xff0c;查了半天&#xff0c;也不知道怎么弄。 在虚拟机&#xff08;Vmware Workstatio…

OSCP靶场--PayDay

OSCP靶场–PayDay 考点(公共exp文件上传密码复用sudo -l all提权) 1.nmap扫描 ## ┌──(root㉿kali)-[~/Desktop] └─# nmap -sV -sC 192.168.153.39 -p- -Pn --min-rate 2500 Starting Nmap 7.92 ( https://nmap.org ) at 2024-04-13 04:52 EDT Nmap scan report for 192…

C语言 | 字符函数和字符串函数

目录&#xff1a; 1. 字符分类函数 2. 字符转换函数 3. strlen的使用和模拟实现 4. strcpy的使用和模拟实现 5. strcat的使用和模拟实现 6. strcmp的使用和模拟实现 7. strncpy函数的使用 8. strncat函数的使用 9. strncmp函数的使用 10. strstr的使用 11. strtok函…

智慧公厕:城市管理的一大创新

公共厕所作为城市基础设施的重要组成部分&#xff0c;不仅仅是提供方便的厕所&#xff0c;更是城市管理的一项创新。随着科技的发展&#xff0c;智慧公厕应运而生。通过物联网、大数据、云计算、网络通信、自动化控制等技术&#xff0c;智慧公厕实现了对公厕内部人体活动状态、…