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

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

  • 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.mzph.cn/news/199879.shtml

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

相关文章

如何使用llm 制作多模态

首先将任何非字符的序列信息使用特殊n个token 编码。 具体编码方法以图像为例子说明&#xff1a; 将固定尺寸图像如256256 的图像分割为1616 的子图像块。 将已知的所有图像数据都分割后进行str将其看做是一个长的字符&#xff0c;而后去重后方式一个词表。 使用特殊1024 个tok…

解决方案:Mac 安装 pip

python3 --version 通过以下命令来下载pip&#xff1a; curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py curl命令允许您指定一个直接下载链接。使用-o选项来设置下载文件的名称。 通过运行以下命令安装下载的包&#xff1a; python3 get-pip.py

【开源】基于JAVA的医院门诊预约挂号系统

项目编号&#xff1a; S 033 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S033&#xff0c;文末获取源码。} 项目编号&#xff1a;S033&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 功能性需求2.1.1 数据中心模块2.1.2…

Day02 Liunx高级程序设计2-文件IO

系统调用 概念 是操作系统提供给用户使其可以操作内核提供服务的一组函数接口 用户态和内核态 其中 ring 0 权限最高&#xff0c;可以使用所有 CPU 指令&#xff0c; ring 3 权限最低&#xff0c;仅能使用 常规 CPU 指令&#xff0c;这个级别的权限不能使用访问硬件资…

深度学习在图像识别中的应用

深度学习在图像识别中的应用 摘要&#xff1a;本文介绍了深度学习在图像识别领域的应用&#xff0c;包括卷积神经网络&#xff08;CNN&#xff09;的基本原理、常见模型以及在图像识别中的优势。并通过实验展示了深度学习在图像识别中的实际应用和效果。 一、引言 随着数字化…

写给初学者的 HarmonyOS 教程 -- 状态管理(@State/@Prop/@Link 装饰器)

State 装饰的变量&#xff0c;或称为状态变量&#xff0c;一旦变量拥有了状态属性&#xff0c;就和自定义组件的渲染绑定起来。当状态改变时&#xff0c;UI 会发生对应的渲染改变&#xff08;类似 Compose 的 mutablestateof &#xff09;。 Prop 装饰的变量可以和父组件建立单…

深度学习在计算机视觉中的应用

深度学习在计算机视觉中的应用 摘要&#xff1a;本文介绍了深度学习在计算机视觉领域的应用&#xff0c;包括目标检测、图像分类、人脸识别等。通过分析深度学习在计算机视觉中的实际应用案例&#xff0c;阐述了深度学习在计算机视觉中的优势和未来发展趋势。 一、引言 计算…

使用rust slint开发桌面应用

安装QT5&#xff0c;过程省略 安装rust&#xff0c;过程省略 创建工程 cargo new slint_demo 在cargo.toml添加依赖 [dependencies] slint "1.1.1" [build-dependencies] slint-build "1.1.1" 创建build.rs fn main() {slint_build::compile(&quo…

8.HTTP工作原理

HTTP是什么 HTTP工作原理 HTTP协议的请求类型和响应状态码 总结 1.HTTP是什么 HTTP超文本传输协议就是在一个网络中上传下载文件的一套规则 2.HTTP工作原理 HTTP超文本传输协议的本质是TCP通信&#xff0c;链接—>请求—>响应—>断开 3.HTTP协议的请求类型和响应状…

Java+Swing+Mysql实现超市管理系统

一、系统介绍 1.开发环境 操作系统&#xff1a;Win10 开发工具 &#xff1a;IDEA2018 JDK版本&#xff1a;jdk1.8 数据库&#xff1a;Mysql8.0 2.技术选型 JavaSwingMysql 3.功能模块 4.系统功能 1.系统登录登出 管理员可以登录、退出系统 2.商品信息管理 管理员可以对商品信息…

Android画布Canvas绘制drawBitmap基于源Rect和目的Rect,Kotlin

Android画布Canvas绘制drawBitmap基于源Rect和目的Rect&#xff0c;Kotlin <?xml version"1.0" encoding"utf-8"?> <androidx.appcompat.widget.LinearLayoutCompat xmlns:android"http://schemas.android.com/apk/res/android"xmlns…

Cannot find module ‘node:url‘报错处理

在运行vite搭建的项目时&#xff0c;遇到Cannot find module node:url’报错。具体错误如图所示&#xff1a; 造成以上问题的原因是node版本较低。Vite 需要 Node.js 版本 14.18&#xff0c;16。 解决方案&#xff1a; 上面是通过nvm切换高版本node。 再次执行运行命令&…

基于Springboot的社区医院管理服务系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的社区医院管理服务系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系…

C语言--每日选择题--Day36

第一题 1. 以下关于指针的说法,正确的是() A&#xff1a;int *const p 与 int const *p等价 B&#xff1a;const int *p 与 int *const p等价 C&#xff1a;const int *p 与 int const *p 等价 D&#xff1a;int *p[10] 与 int (*p)[10] 等价 答案及解析 C const 在*的左侧&…

缓存穿透、击穿、雪崩

缓存穿透&#xff1a; 指的是恶意用户或攻击者通过请求不存在于缓存和后端存储中的数据来使得所有请求都落到后端存储上&#xff0c;导致系统瘫痪。 解决方案&#xff1a; 通常包括使用布隆过滤器或者黑白名单等方式来过滤掉无效请求&#xff0c;以及在应用程序中加入缓存预热…

SpringSecurity6 | 默认用户生成

SpringSecurity6 | 默认用户生成 ✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; Java…

从0开始学Spring、Springboot总结笔记(持续更新中~)

文章目录 一.基于SpringBoot进行Web开发入门1.IDEA编译器中创建springboot工程扩展&#xff1a;如何解决pom.xml文件中“找不到Maven插件”的问题&#xff1f; 2.Springboot项目如何编写请求类和请求方法并启动访问编写请求类和请求方法启动Springboot访问 一些学习资源参考 一…

如何搭建eureka-server

在Spring Cloud项目的pom文件中添加eureka-server的starter依赖坐标 <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://ma…

人工智能学习4(特征选择)

编译工具&#xff1a;PyCharm 有些编译工具在绘图的时候不需要写plt.show()或者是print就可以显示绘图结果或者是显示打印结果&#xff0c;pycharm需要&#xff08;matplotlib.pyplot&#xff09; 文章目录 编译工具&#xff1a;PyCharm 特征选择嵌入法特征选择练习&#xff…

云原生的 CI/CD 框架tekton - Trigger(二)

上一篇为大家详细介绍了tekton - pipeline&#xff0c;由于里面涉及到的概念比较多&#xff0c;因此需要好好消化下。同样&#xff0c;今天在特别为大家分享下tekton - Trigger以及案例演示&#xff0c;希望可以给大家提供一种思路哈。 文章目录 1. Tekton Trigger2. 工作流程3…