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

news2025/1/17 0:50:55

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

  • 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;
}
  • 一定要做到:低耦合、高内聚。

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

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

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

相关文章

bootstrap中的图标元素可以免费使用

Available glyphsIncludes over 250 glyphs in font format from the Glyphicon Halflings set. Glyphicon 网址如下&#xff1a; Components Bootstrap

17.认识下Docker之docker的核心原理(2)

1.容器-我的小世界 不知道大家看没看过小说《完美时间》&#xff0c;里面石昊经常进入一个小世界在里面与世隔绝的修炼或者战斗&#xff0c;总之就是在一个完全封闭的空间里做他想做的事情而与外界隔离&#xff0c;不受侵扰。通过前面的分析我们知道&#xff0c;Namepace让应用…

【代码随想录】算法训练计划39

dp 1、62. 不同路径 题目&#xff1a; 求路径方案多少个 思路&#xff1a; 这道题就有点dp了哈 func uniquePaths(m int, n int) int {//dp&#xff0c;写过,代表的是多少种// 初始化dp : make([][]int, m)for i : range dp {dp[i] make([]int, n)dp[i][0] 1 // 代表到…

【5】PyQt按钮

QPushButton 常见的按钮实现类包括:QPushButton、QRadioButton和QCheckBox QPushButton是最普通的按钮控件&#xff0c;可以响应一些用户的事件 from PyQt5.QtWidgets import QApplication, QWidget, QPushButton import sysdef func():print("按下按钮啦&#xff0c;火…

MPC模型预测控制理论与实践

一、基本概念 最有控制的动机是在约束条件下达到最优的系统表现。 模型预测控制&#xff08;MPC&#xff0c;Model Predictive Control&#xff09;是通过模型来预测系统在某一未来时间段内的表现来进行优化控制&#xff0c;多用于数位控制&#xff0c;通常用离散型状态空间表…

随机链表的复制[中等]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给你一个长度为n的链表&#xff0c;每个节点包含一个额外增加的随机指针random&#xff0c;该指针可以指向链表中的任何节点或空节点。构造这个链表的深拷贝。深拷贝应该正好由n个全新节点组成&#xff0c;其中每个新节点的值都设为…

【论文笔记】A Transformer-based Approach for Source Code Summarization

A Transformer-based Approach for Source Code Summarization 1. Introduction2. Approach2.1 ArchitectureSelf-AttentionCopy Attention 2.2 Position Representations编码绝对位置编码成对关系 1. Introduction 生成描述程序功能的可读摘要称为源代码摘要。在此任务中&…

C++ day55 判断子序列 不同的子序列

题目1&#xff1a;392 判断子序列 题目链接&#xff1a;判断子序列 对题目的理解 判断字符串s是否为t的子序列 字符串s和字符串t的长度大于等于0&#xff0c;字符串s的长度小于等于字符串t的长度&#xff0c;本题其实和最长公共子序列的那道题很相似&#xff0c;相当于找两…

面试就是这么简单,offer拿到手软(四)—— 常见java152道基础面试题

面试就是这么简单&#xff0c;offer拿到手软&#xff08;一&#xff09;—— 常见非技术问题回答思路 面试就是这么简单&#xff0c;offer拿到手软&#xff08;二&#xff09;—— 常见65道非技术面试问题 面试就是这么简单&#xff0c;offer拿到手软&#xff08;三&#xff…

【数据结构(七)】查找算法

文章目录 查找算法介绍1. 线性查找算法2. 二分查找算法2.1. 思路分析2.2. 代码实现2.3. 功能拓展 3. 插值查找算法3.1. 前言3.2. 相关概念3.3. 实例应用 4. 斐波那契(黄金分割法)查找算法4.1. 斐波那契(黄金分割法)原理4.2. 实例应用 查找算法介绍 在 java 中&#xff0c;我们…

【Midjourney实战】| 新年礼盒元素设计

文章目录 1 初步提示词2 润色提示词3 提示词发散联想 这期实践任务&#xff0c;我们想去做一个新年礼盒的效果&#xff0c;最后我们想把不同元素拼在一起&#xff0c;方便后期进行新年的相关设计 1 初步提示词 提示词初步我们乍一想&#xff0c;肯定要包括主体元素礼盒 新年礼…

APOLLO自动驾驶技术沙龙:未来已来,共创智能交通新时代

在这次Apollo会议上&#xff0c;我深刻地感受到了人工智能自动驾驶技术领域的最新进展和未来趋势。作为一名从事软件开发工作的人员&#xff0c;我深感荣幸能够参加这次盛会。 前言 本次活动是百度Apollo社区工程师齐聚首钢Park&#xff0c;带来现场实操与技术分享。主要围绕Ap…

好用的挂耳式蓝牙耳机有哪些?四款好用高性价比的耳机推荐

随着生活节奏的加快&#xff0c;挂耳式蓝牙耳机真的是越来越不可或缺了&#xff0c;不管是坐地铁、步行还是运动&#xff0c;一副好用的挂耳式蓝牙耳机都能让你感觉自己像是生活里的主角。但市面上的选择实在是太多了&#xff0c;简直让人眼花缭乱&#xff0c;不过我找了四款真…

仓库管理系统【GUI/Swing+MySQL】(Java课设)

系统类型 Swing窗口类型Mysql数据库存储数据 使用范围 适合作为Java课设&#xff01;&#xff01;&#xff01; 部署环境 jdk1.8Mysql8.0Idea或eclipsejdbc 运行效果 本系统源码地址&#xff1a; 更多系统资源库地址&#xff1a;更多Java课设系统 更多系统运行效果展示…

很全面 影响无人机自动返航的因素总结

在无人机技术不断成熟的今天&#xff0c;自主返航技术成为保障飞行安全的一种重要工具。无人机在多种情况下能够智能判断&#xff0c;主动实施返航动作&#xff0c;为用户提供更加可靠的飞行保障。以下是一些常见的无人机自动返航场景&#xff0c;让我们深入了解这项技术背后的…

玩转数据8:数据质量管理与数据清洗的实践

引言 在当今数字化时代&#xff0c;数据质量管理和数据清洗对于企业和组织来说变得至关重要。随着大数据的快速增长和数据驱动决策的普及&#xff0c;确保数据的准确性、一致性和完整性成为保证业务成功的关键因素。本文将探讨数据质量管理和数据清洗的概念、目标以及其在Java…

U-Net网络模型改进(添加通道与空间注意力机制)---亲测有效,指标提升

U-Net网络模型&#xff08;注意力改进版本&#xff09; 这一段时间做项目用到了U-Net网络模型&#xff0c;但是原始的U-Net网络还有很大的改良空间&#xff0c;在卷积下采样的过程中加入了通道注意力和空间注意力 。 常规的U-net模型如下图&#xff1a; 红色箭头为可以添加的…

电表峰谷平是怎么分时间的?

电表的峰谷平时间是指电力公司根据电力需求的不同&#xff0c;将一天的时间划分为不同的时段&#xff0c;以此来确定不同时间段内的电费价格。这种不同时段对应不同电费价格的制度&#xff0c;旨在更好地平衡电力供需&#xff0c;促进能源的高效利用。 首先&#xff0c;我们来了…

记录一下Mac配置SpringBoot开发环境

由于很多项目喜欢使用传统的 Java 8 进行开发&#xff0c;而且 Java 8 的稳定性也是经过长久考验的&#xff0c;我们接下来就尝试一下&#xff0c;在一台新的 Mac 中配置 Java 环境&#xff0c;并且开始创建 SpringBoot 项目。 首先&#xff0c;去 Oracle 官网下载 java8 JDK …

springboot详解Mybatis-Plus中分页插件PaginationInterceptor标红

1.问题描述 在springboot项目中&#xff0c;类中引用PaginationInterceptor&#xff0c;标红&#xff0c;如下图所示&#xff1a; 2.问题分析 可能是因为pom.xml中的配置原因&#xff0c;导致不支持PaginationInterceptor 3.解决问题 更换版本后 更换后&#xff0c;记得Rel…