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

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

  • 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 kittens

    return 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 working
    
    human.work();  // 输出: Human is working
    human.eat();   // 输出: Human is eating

    return 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 started

    return 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 conducted

    return 0;
}
  • 一定要做到:低耦合、高内聚。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用C语言创建高性能网络爬虫IP池

目录 一、引言 二、IP池的设计 1、需求分析 2、架构设计 3、关键技术 三、IP池的实现 1、存储实现 2、调度实现 3、通信实现 4、异常处理实现 四、代码示例 五、性能优化 六、测试与分析 七、结论 一、引言 随着互联网的快速发展&#xff0c;网络爬虫成为了获取…

2-1、地址加法器CS:IP

语雀原文链接 文章目录 1、CPU组成2、通用寄存器16位寄存器的存储16位寄存器兼容8位word 和 byte进位问题 3、地址加法器不同的段地址和偏移地址表示同一个物理地址偏移地址的范围一个段的起始地址一定是16的倍数 4、CS:IPCS IP工作过程jmp修改CS:IP 5、DS和[address]DS和[add…

高级搜索——伸展树Splay详解

文章目录 伸展树Splay伸展树Splay的定义局部性原理Splay的伸展操作逐层伸展双层伸展zig-zig/zag-zagzig-zag/zag-zigzig/zag双层伸展的效果与效率 伸展树的实现动态版本实现递增分配器节点定义Splay类及其接口定义伸展操作左单旋右单旋右左/左右双旋伸展 查找操作删除操作插入操…

Spring 保姆级带你认识,让你如何轻松应对面试官

Spring 保姆级带你认识&#xff0c;让你如何轻松应对面试官 1.Spring是什么&#xff1f;作用是什么&#xff1f; Spring是一个轻量级的JavaEE框架&#xff0c;它主要解决企业应用中的复杂性问题。Spring框架有三个核心部分&#xff1a;IoC容器、AOP和数据访问/集成层。Spring…

java--抽象类

1.什么是抽象类 ①在java中有一个关键字叫&#xff1a;abstract&#xff0c;它就是抽象的意思&#xff0c;可以用它修饰类、成员方法。 ②abstract修饰类&#xff0c;这个类就是抽象类&#xff1b;修饰方法&#xff0c;这个方法就是抽象方法。 2.抽象类的注意事项、特点 ①抽…

Vue3-ElementPlus按需导入

1.安装 pnpm add element-plus 2.配置按需导入&#xff1a; 官方文档&#xff1a;快速开始 | Element Plus 按照官网按需导入中的自动导入步骤来进行 pnpm add -D unplugin-vue-components unplugin-auto-import 观察Vite代码与原vite文件的差别&#xff0c;将原vite文件中没…

[数据结构]-map和set

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、键值对…

【数据结构】二叉树的实现

目录 1. 前言2. 二叉树的实现2.1 创建一棵树2.2 前序遍历2.2.1 分析2.2.2 代码实现2.2.3 递归展开图 2.3 中序遍历2.3.1 分析2.3.2 代码实现2.3.3 递归展开图 2.4 后序遍历2.4.1 分析2.4.2 代码实现2.4.3 递归展开图 2.5 求节点个数2.5.1 分析2.5.2 代码实现 2.6 求叶子节点个数…

Linux 环境下的性能测试——top与stress

对于Linux 环境&#xff0c;top命令是使用频繁且信息较全的命令&#xff0c; 它对于所有正在运行的进行和系统负荷提供实时更新的概览信息。stress是个简单且全面的性能测试工具。通过它可以模拟各种高负载情况。 通过top与stress这两个命令的结合使用&#xff0c;基本可以达到…

解决:ModuleNotFoundError: No module named ‘exceptions’

解决&#xff1a;ModuleNotFoundError: No module named ‘exceptions’ 文章目录 解决&#xff1a;ModuleNotFoundError: No module named exceptions背景报错问题翻译&#xff1a;报错位置代码报错原因解决方法今天的分享就到此结束了 背景 在使用之前的代码时&#xff0c;报…

报表控件Stimulsoft 操作演示:访问编译的报告

使用编译计算模式的报告能够执行使用报告脚本语言实现的各种脚本。然而&#xff0c;这些场景并不总是安全的&#xff0c;从网络安全的角度来看&#xff0c;可能会导致负面情况。经过分析情况&#xff0c;我们决定加强有关编译模式报告的安全策略。但让我们一步一步来。顺便说一…

IDEA版SSM入门到实战(Maven+MyBatis+Spring+SpringMVC) -Mybatis核心配置详解

第一章 Mybatis核心配置详解【mybatis-config.xml】 1.1 核心配置文件概述 MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 1.2 核心配置文件根标签 没有实际语义&#xff0c;主要作用&#xff1a;所有子标签均需要设置在跟标签内部 1.3 核心配置文件…

【数电笔记】17-具体函数的卡诺图填入

目录 说明&#xff1a; 用卡诺图表示逻辑函数 1. 基本步骤 2. 例题 2.1 例1-真值表转换卡诺图 2.2 例2-标准与或式画卡诺图 2.3 例3-非标准与或式画卡诺图&#xff08;常见,重点掌握&#xff09; 说明&#xff1a; 笔记配套视频来源&#xff1a;B站&#xff1b;本系列笔…

学生档案管理系统研究

摘 要 学生档案管理系统是一个教育单位不可缺少的部分,它的内容对于学校的决策者和管理者来说都至关重要,所以学生档案管理系统应该能够为用户提供充足的信息和快捷的查询手段。但一直以来人们使用传统人工的方式管理文件档案&#xff0c;这种管理方式存在着许多缺点,如:效率低…

gpt阅读论文利器

1. txyz.ai 读论文 严伯钧 3. consensus 两亿科学论文的资源库. 用英文. 中国经济发展, 美国加州没有,减肥没有. 2. chrome插件 gpt sidebar 3. gpt academic 论文润色和学术翻译 ,一键输出公式. 英语口语8000句. 托福备考计划表. 百词斩托福. 薄荷外刊. 分区笔记精读法.…

java--接口的其他细节

1.jdk8开始&#xff0c;接口新增了三种形式的方法 ①默认方法(实例方法)&#xff1a;使用用default修饰&#xff0c;默认会被加上public修饰。注意&#xff1a;只能使用接口的实现类对象调用 ②私有方法&#xff1a;必须用private修饰(jdk9开始才支持) ③类方法(静态方法)&a…

数字化车间|用可视化技术提升车间工作效率

数字化车间正在成为现代制造业的重要组成部分。随着科技的不断进步&#xff0c;传统的车间生产方式逐渐地被数字化和自动化取代。数字化车间将机器和软件进行整合&#xff0c;实现了生产过程的高效、精确和可追溯。在数字化车间中&#xff0c;机器之间可以进行无缝的通信和协作…

vue: 线上项目element-ui的icon偶尔乱码问题

线上环境偶尔会复现&#xff0c; 具体&#xff1a; 一般使用不会出现这个问题&#xff0c;因为一般引入的是element-ui的css文件&#xff0c;问题出在于为了主题色变化啊&#xff0c;需要用到scss变量引入了scss文件。 import “~element-ui/packages/theme-chalk/src/index”…

XCharts——Unity上最好用的免费开源图表插件!(一)基本介绍

只讲实用干货&#xff01;&#xff01;&#xff01;&#xff08;过于细节的或是未提及到的可直接问&#xff09; 目录 XCharts介绍 插件简介 插件下载 XCharts基本使用 类型介绍 1.折线图&#xff08;LineChart&#xff09; 2.柱形图&#xff08;BarChart&#xff09; …

Spring---事务

事务 学习了MySQL的朋友应该大致了解事务的含义。所谓事务就是一系列操作&#xff0c;要么都执行&#xff0c;要么都不执行。在spring中事务有两种形式&#xff1a;声明式事务和编程式事务。一般使用声明式事务&#xff0c;用的比较多的就是注解Transactional。如下图所示&…