设计模式——七大设计原则

设计模式——七大设计原则

  • 1、单一职责原则(SRP)
  • 2、开放封闭原则(OCP)
  • 3、依赖倒转原则(DIP)
  • 4、里氏替换原则 (LSP)
  • 5、接口隔离原则 (ISP)
  • 6、合成/聚合复用原则 (CARP)
  • 7、迪米特法则 (LoD)

了解 设计模式 的朋友们,想必都听说过“七大设计原则”吧。我们在进行程序设计的时候,要尽可能地保证程序的 可扩展性可维护性可读性,最经典的 23 种设计模式中或多或少地都在使用这些设计原则,也就是说,设计模式是站在设计原则的基础之上的。所以在学习设计模式之前,很有必要对这些设计原则先做一下了解。

在这里插入图片描述

1、单一职责原则(SRP)

There should never be more than one reason for a class to change.
理解:一个类只负责一项职责,不同的类具备不同的职责,各司其职

    面向对象三大特性之一的 封装 指的就是将单一事物抽象出来组合成一个类,所以我们在设计类的时候每个类中处理的是单一事物而不是某些事物的集合。

    设计模式中所谓的 单一职责原则(Single Responsibility Principle - SRP),就是对一个类而言,应该仅有一个引起它变化的原因,其实就是将这个类所承担的职责单一化。如果一个类承担的职责过多,就等于把这些 职责耦合 到了一起,一个职责的变化可能会 削弱或者抑制 这个类完成其他职责的能力。这种耦合会导致设计变得脆弱,当变化发生时,设计会遭受到意想不到的破坏

#include <iostream>
#include <string>// 单一职责原则示例:一个类只负责一个职责class File {
public:void writeToFile(const std::string& data) {// 写入文件的具体实现std::cout << "Writing to file: " << data << std::endl;}
};class Logger {
public:void log(const std::string& message) {// 记录日志的具体实现std::cout << "Logging: " << message << std::endl;}
};int main() {File file;file.writeToFile("Data to be written");Logger logger;logger.log("Log message");return 0;
}

    软件设计真正要做的事情就是,发现根据需求发现职责,并把这些职责进行分离,添加新的类,给当前类减负,越是这样项目才越容易维护。杜绝万能类万能函数!!!

2、开放封闭原则(OCP)

Software entities like classes,modules and functions should be open for extension but closed for modifications.
理解:类、模块、函数,对 扩展开放,对 修改封闭

    开放 – 封闭原则 (Open/Closed Principle - OCP) 说的是软件实体(类、模块、函数等)可以扩展,但是不可以修改。也就是说对于扩展是开放的,对于修改是封闭的

    该原则是程序设计的一种理想模式,在很多情况下无法做到完全的封闭。但是作为设计人员,应该能够对自己设计的模块在哪些位置产生何种变化了然于胸,因此 需要在这些位置创建 抽象类 来隔离以后发生的这些同类变化其实就是对 多态 的应用,创建新的子类并重写父类虚函数,用以更新处理动作)。

此处的 抽象类,其实并不等价与C++中完全意义上是 抽象类 (需要有纯虚函数),这里所说的 抽象类 只需要包含虚函数纯虚函数非纯虚函数)能够实现 多态 即可。

#include <iostream>
#include <vector>// 开闭原则示例:通过抽象类和继承来实现开闭原则class Shape {
public:virtual void draw() const = 0;
};class Circle : public Shape {
public:void draw() const override {std::cout << "Drawing Circle" << std::endl;}
};class Square : public Shape {
public:void draw() const override {std::cout << "Drawing Square" << std::endl;}
};class Drawing {
public:void drawShapes(const std::vector<Shape*>& shapes) const {for (const auto& shape : shapes) {shape->draw();}}
};int main() {Circle circle;Square square;Drawing drawing;std::vector<Shape*> shapes = {&circle, &square};drawing.drawShapes(shapes);return 0;
}

    开放 – 封闭原则 是面向对象设计的核心所在,这样可以给我们设计出的程序带来巨大的好处,使其可维护性可扩展性可复用性灵活性更好

3、依赖倒转原则(DIP)

High level modules should not depends upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.
理解:高层模块 不应该依赖于底层模块(具体),而 应该依赖于抽象。(面向接口编程

    关于依赖倒转原则,对应的是两条非常抽象的描述:

  1. 高层模块不应该依赖低层模块,两个都应该依赖抽象
  2. 抽象不应该依赖细节细节应该依赖抽象

    先用人话解释一下这两句话中的一些抽象概念:

  • 高层模块:可以理解为上层应用,就是业务层的实现;
  • 低层模块:可以理解为底层接口,比如封装好的API动态库等;
  • 抽象:指的就是抽象类或者接口(在C++中没有接口,只有抽象类)。

先举一个 高层模块 依赖 低层模块的例子:

大聪明的项目组接了一个新项目,低层使用的是 MySql数据库接口高层基于这套接口对数据库表进行了添删查改,实现了对业务层数据的处理。而后由于某些原因,数据超大规模和高并发的需求,所以更换了 Redis 数据库,由于低层的数据库接口变了,高层代码的数据库操作部分是直接调用了低层的接口,因此也需要进行对应的修改,无法实现对高层代码的直接复用,大聪明欲哭无泪。

  • 通过上面的例子可以得知,当依赖的低层模块变了就会牵一发而动全身,如果这样设计项目架构,对于程序猿来说,其工作量无疑是很重的。

在这里插入图片描述

// 依赖倒置原则示例:高层模块不应该依赖于底层模块,二者都应该依赖于抽象// 数据库接口(抽象类)
class Database {
public:virtual void connect() = 0;virtual void query(const std::string& sql) = 0;virtual void disconnect() = 0;
};// MySQL 数据库实现(低层模块)
class MySQLDatabase : public Database {
public:void connect() override {// 连接 MySQL 数据库的具体实现}void query(const std::string& sql) override {// 执行 MySQL 查询的具体实现}void disconnect() override {// 断开 MySQL 数据库连接的具体实现}
};// Redis 数据库实现(低层模块)
class RedisDatabase : public Database {
public:void connect() override {// 连接 Redis 数据库的具体实现}void query(const std::string& command) override {// 执行 Redis 命令的具体实现}void disconnect() override {// 断开 Redis 数据库连接的具体实现}
};//高层模块
class AppService {
private:Database* database;public:AppService(Database* db) : database(db) {}void performTask() {database->connect();database->query("SELECT * FROM data");// 执行其他操作database->disconnect();}
};
  • 如果要搞明白这个案例的解决方案以及 抽象和细节 之间的依赖关系,需要先了解另一个原则 — 里氏替换原则

4、里氏替换原则 (LSP)

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
理解:父类可被子类替换,但 反之不一定成立。

所谓的里氏替换原则就是子类类型必须能够替换掉它们的父类类型

    关于这个原理的应用其实也很常见,比如在Qt中,所有窗口类型的类的构造函数都有一个 QWidget* 类型的参数(QWidget类 是所有窗口的 基类),通过这个参数指定当前窗口的父对象。虽然参数是窗口类的基类类型,但是我们在给其指定实参的大多数时候,指定的都是 子类的对象,其实也就是相当于使用子类类型 替换掉了 它们的 父类类型

    这个原则的要满足的第一个条件就是 继承,其次还要求子类继承的所有父类的属性和方法对于子类来说都是合理。关于这个是否合理下面举个栗子:

比如,对于哺乳动物来说都是胎生,但是有一种特殊的存在就是鸭嘴兽,它虽然是哺乳动物,但是是卵生


在这里插入图片描述


如果我们设计了两个类:哺乳动物类鸭嘴兽类,此时能够让鸭嘴兽类继承哺乳动物类吗?

  • 答案肯定是否定的,因为如果我们这么做了,鸭嘴兽就继承了胎生属性,这个属性和它自身的情况是不匹配的。
  • 如果想要遵循里氏替换原则,我们就不能让着两个类有继承关系。

如果我们创建了其它的胎生的哺乳动物类,那么它们是可以继承哺乳动物这个类的,在实际应用中就可以使用子类替换掉父类,同时功能也不会受到影响,父类实现了复用,子类也能在父类的基础上增加新的行为,这个就是 里氏替换原则

#include <iostream>// 里氏替换原则示例:派生类可以替代基类// 基类:哺乳动物
class Mammal {
protected:bool isViviparous;  // 出生方式为胎生public:virtual void giveBirth() const {std::cout << "Giving birth" << std::endl;}
};// 派生类:狗
class Dog : public Mammal {
public:// 重写基类的 giveBirth 方法void giveBirth() const override {std::cout << "Dog giving birth to puppies" << std::endl;}
};// 派生类:猫
class Cat : public Mammal {
public:// 重写基类的 giveBirth 方法void giveBirth() const override {std::cout << "Cat giving birth to kittens" << std::endl;}
};// 函数:繁殖哺乳动物
void reproduce(const Mammal& animal) {animal.giveBirth();
}int main() {Dog myDog;Cat myCat;// 使用 Dog 对象std::cout << "Dog: ";reproduce(myDog);  // 输出: Dog giving birth to puppies// 使用 Cat 对象std::cout << "Cat: ";reproduce(myCat);  // 输出: Cat giving birth to kittensreturn 0;
}

    上面在讲 依赖倒转原则 的时候说过,抽象不应该依赖细节,细节应该依赖抽象。也就意味着我们应该对细节进行封装,在C++中就是将其放到一个抽象类中(C++中没有接口,不能像Java一样封装成接口),每个细节就相当于上面例子中的哺乳动物的一个特性,这样一来这个抽象的哺乳动物类就成了项目架构中高层和低层的桥梁,将二者整合到一起。

  • 抽象类中提供的接口是固定不变的
  • 低层模块是抽象类的子类,继承了抽象类的接口,并且可以重写这些接口的行为
  • 高层模块想要实现某些功能,调用的是抽象类中的函数接口,并且是通过抽象类的父类指针引用其子类的实例对象(用子类类型替换父类类型),这样就实现了多态

在这里插入图片描述
    基于 依赖倒转原则 将项目的结构换成上图的这种模式之后,低层模块发生变化,对应高层模块是没有任何影响的,这样程序猿的工作量降低了,代码也更容易维护(说白了,依赖倒转原则就是对多态的典型应用)。

5、接口隔离原则 (ISP)

The dependency of one class to another one should depend on the smallest possible interface.
理解:使用多个专门的接口,而不要使用一个单一的(大)接口(接口单一职责

    这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

(在C++中没有接口,只有抽象类)

#include <iostream>// 接口隔离原则示例:客户端不应该被迫依赖它不使用的接口class Worker {
public:virtual void work() const = 0;
};class Eater {
public:virtual void eat() const = 0;
};class Robot : public Worker {
public:void work() const override {std::cout << "Robot is working" << std::endl;}
};class Human : public Worker, public Eater {
public:void work() const override {std::cout << "Human is working" << std::endl;}void eat() const override {std::cout << "Human is eating" << std::endl;}
};int main() {Robot robot;Human human;robot.work();  // 输出: Robot is workinghuman.work();  // 输出: Human is workinghuman.eat();   // 输出: Human is eatingreturn 0;
}

6、合成/聚合复用原则 (CARP)

Strive for a design where you compose classes from smaller, more independent units, rather than inheriting from a single, monolithic base class.
理解:尽量 使用组合/聚合,而 不是继承

#include <iostream>// 合成/聚合复用原则示例:优先使用合成/聚合,而不是继承class Engine {
public:void start() const {std::cout << "Engine started" << std::endl;}
};class Car {
private:Engine engine;public:void start() const {engine.start();std::cout << "Car started" << std::endl;}
};int main() {Car car;car.start();  // 输出: Engine started, Car startedreturn 0;
}

7、迪米特法则 (LoD)

Only talk to you immediate friends.
理解:尽量 减少对象之间的交互,从而减小类之间的耦合。

    迪米特法则 ( Law of Demeter - LoD ) 又叫 最少知道原则 ,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的 public 方法,不对外泄露任何信息。

迪米特法则还有个更简单的定义:只与直接的朋友通信。

  • 直接的朋友 :每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系, 我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合 等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而 出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量 的形式出现在类的内部。
#include <iostream>// 迪米特法则示例:一个对象应该对其它对象有尽可能少的了解class Teacher {
public:void teach() const {std::cout << "Teaching..." << std::endl;}
};class Student {
public:void learn() const {std::cout << "Learning..." << std::endl;}
};class School {
private:Teacher teacher;Student student;public:void conductClass() const {teacher.teach();student.learn();std::cout << "Class is conducted" << std::endl;}
};int main() {School school;school.conductClass();  // 输出: Teaching..., Learning..., Class is conductedreturn 0;
}
  • 一定要做到:低耦合、高内聚。

注:仅供学习参考,如有不足欢迎指正!

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

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

相关文章

Qt/C++音视频开发57-切换音视频轨道/切换节目流/分别切换音频视频轨道

一、前言 对各种音视频文件格式的支持&#xff0c;是一个播放器的基础功能。一般的音视频文件只有1路流&#xff0c;比如音频文件只有1路音频流&#xff0c;视频文件只有1路音频1路视频流&#xff0c;实践过程中发现&#xff0c;还有一种ts格式的文件&#xff0c;可能有多路流…

Python包管理器PIP用法大全

pip是Python的包管理器&#xff0c;用于安装和管理Python包。以下是一些常用的基本的pip命令&#xff0c;分享给大家&#xff0c;希望对大家使用pip有所帮助。 文章目录 pip installpip uninstallpip listpip searchpip downloadpip configpip freezepip checkpip wheelpip ha…

网络安全攻击预警/态势预测算法汇总

总结&#xff1a; 网络安全攻击预警/态势预测算法众多&#xff0c;主要包括&#xff1a; 基于统计学的算法&#xff1a;协方差矩阵、马尔可夫模型等&#xff1b; 基于机器学习的算法&#xff1a;贝叶斯网络、聚类算法、支持向量机SVM、遗传算法、层次分析法AHP、决策树等&am…

Matlab 曲线动态绘制

axes(handles.axes1); % 选定所画坐标轴 figure也可 h1 animatedline; h1.Color b; h1.LineWidth 2; h1.LineStyle -; % 线属性设置 for i 1 : length(x)addpoints(h1,x(i),y(i)); % x/y为待绘制曲线数据drawnow;pause(0.01); % 画点间停顿 end 示例&#xff1a; figure…

观海微电子---线路腐蚀的起因与对策

线路腐蚀的原理&#xff1a; 在线路表面的污染物中含有金属元素的离子或金属化合物&#xff0c; 在潮湿的空气中这些污染物与线路之间的冷凝水连成微电池&#xff0c;引发电化学反应&#xff0c;产品通电的情况下反应进行得更快&#xff0c;耗损线路导致线路腐蚀形成断线。 腐…

WordPress外贸站优化工具,WordPress外贸SEO优化方法

WordPress外贸站是跨国企业拓展市场、提升品牌知名度的理想选择。然而&#xff0c;如何通过SEO优化、原创文章生成以及留心站点优化的事项&#xff0c;成为众多站长关注的焦点。 SEO&#xff0c;即搜索引擎优化&#xff0c;是提高网站在搜索引擎结果中排名的关键。首先&#x…

linux的权限741

741权限 在 Linux 中&#xff0c;文件和目录的权限由三组权限来定义&#xff0c;分别是所有者&#xff08;Owner&#xff09;、所属组&#xff08;Group&#xff09;和其他用户&#xff08;Others&#xff09;。每一组权限又分为读&#xff08;Read&#xff09;、写&#xff0…

前端大文件上传webuploader(react + umi)

使用WebUploader还可以批量上传文件、支持缩略图等等众多参数选项可设置&#xff0c;以及多个事件方法可调用&#xff0c;你可以随心所欲的定制你要的上传组件。 分片上传 1.什么是分片上传 分片上传&#xff0c;就是将所要上传的文件&#xff0c;按照一定的大小&#xff0c;将…

python实现pdf转word、word转pdf

我的博客 文章首发于公众号&#xff1a;小肖学数据分析 Python自动化办公通常对常用的办公软件文档格式进行操作&#xff0c;比如Word和PDF。 很多软件都需要付费&#xff0c;作为程序员&#xff0c;怎么可能付费。 下面是一个简单示例&#xff0c;如何在Python中将Word文档…

Java抽象类(abstract class)和接口(interface)的区别——面试

1.抽象类&#xff08;abstract class&#xff09;和接口&#xff08;interface&#xff09;的区别&#xff1a; 抽象类可以有构造方法&#xff0c;接口中不能有构造方法。 抽象类中可以有普通成员变量&#xff0c;接口中没有普通成员变量。抽象类中可以包含非抽象的普通方法&am…

总结|哪些平台有大模型知识库的Web API服务

截止2023/12/6 笔者个人的调研&#xff0c;有三家有大模型知识库的web api服务&#xff1a; 平台类型文档数量文档上传并解析的结构api情况返回页码文心一言插件版多文档有问答api&#xff0c;文档上传是通过网页进行上传有&#xff0c;而且是具体的chunk id&#xff0c;需要设…

图像处理中的角点检测Python-OpenCV 中的实现

马丁亚当斯 (Martin Adams)在Unsplash上拍摄的照片 一、说明 在图像处理的背景下&#xff0c;“特征”可以直观地理解为图像中易于识别并用于表示图像的独特或独特的部分。将特征视为图像中使其可区分的“地标”或“焦点”。为了使这一点更具关联性&#xff0c;请考虑一下您如…

js基础之事件监听案例入门

事件绑定 代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </head&…

c语言五子棋

下面是一个简单的C语言五子棋实现示例&#xff1a; #include <stdio.h>#include <stdlib.h>#define BOARD_SIZE 15char board[BOARD_SIZE][BOARD_SIZE];void init_board() { int i, j; for (i 0; i < BOARD_SIZE; i) { for (j 0; j < BOARD_…

0X05

打开题目 点击完登录和注册都没有什么反应&#xff0c;所以先扫一下看看 在出现admin.php后就截止了&#xff0c;访问看看,进入后台。。 尝试一下弱口令 admin/12345 或者是demo/demo 设计中-自定义->右上角导出主题 找到一个导出的点&#xff0c;下载了一个1.zip压缩包…

智能优化算法应用:基于卷尾猴算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于卷尾猴算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于卷尾猴算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.卷尾猴算法4.实验参数设定5.算法结果6.参考文献7.…

zookeeper1==zookeeper源码阅读,源码启动ZK集群

下载源码 Tags apache/zookeeper GitHub https://codeload.github.com/apache/zookeeper/zip/refs/tags/release-3.9.1 JDK8 MAVEN3.8.6 mvn -DskipTeststrue package 配置ZK1 zkServer.cmd中指出了启动类是 QuorumPeerMain QuorumPeer翻译成集群成员比较合理&#xf…

【Spring】依赖注入之属性注入详解

前言&#xff1a; 我们在进行web开发时&#xff0c;基本上一个接口对应一个实现类&#xff0c;比如IOrderService接口对应一个OrderServiceImpl实现类&#xff0c;给OrderServiceImpl标注Service注解后&#xff0c;Spring在启动时就会将其注册成bean进行统一管理。在Co…

【计算机网络学习之路】URL概念及组成

目录 一. URL是什么 二. URL的组成 三. encode和decode 前言 本系列文章是计算机网络学习的笔记&#xff0c;欢迎大佬们阅读&#xff0c;纠错&#xff0c;分享相关知识。希望可以与你共同进步。 本篇讲解使用浏览器不可或缺的部分——URL 一. URL是什么 域名及DNS 我们在…

SQL Server 数据库,创建触发器避免数据被更改

5.4触发器 触发器是一种特殊类型的存储过程&#xff0c;当表中的数据发生更新时将自动调用&#xff0c;以响应INSERT、 UPDATE 或DELETE 语句。 5.4.1什么是触发器 1.触发器的概念 触发器是在对表进行插入、更新或删除操作时自动执行的存储过程&#xff0c;触发器通常用于强…