首页 > 其他分享 >常见设计原则

常见设计原则

时间:2024-12-25 23:31:26浏览次数:5  
标签:std 原则 void 常见 接口 Bird class 设计 public

常用设计原则目录

单一职责原则

  • 单一职责原则(Single Responsibility Principle, SRP)
  • 定义:
    一个类应该只有一个引起它变化的原因。即一个类应该只负责一项职责。
  • 解释:
    这意味着一个类应该专注于一个功能或一个业务逻辑。如果一个类承担了多个不同的职责,那么对其中一个职责的修改可能会影响到其他职责,导致难以维护和测试。例如,一个类既负责用户数据的存储又负责用户数据的验证,当存储逻辑需要修改时,可能会影响到验证逻辑,反之亦然。
    例如,在一个电商系统中, OrderService 类应该只负责订单的处理,如创建订单、修改订单、取消订单等操作,而不应该同时负责处理用户的支付和商品的库存管理。
#include <iostream>
#include <string>

// 用户验证类,只负责用户验证功能
class UserValidator {
public:
    bool validate(const std::string& username, const std::string& password) {
        // 简单的验证逻辑,这里仅作示例
        if (username == "admin" && password == "123456") {
            return true;
        }
        return false;
    }
};

// 用户存储类,只负责用户存储功能
class UserStorage {
public:
    void save(const std::string& username, const std::string& password) {
        // 简单的存储逻辑,这里仅作示例
        std::cout << "Saving user: " << username << " with password: " << password << std::endl;
    }
};

int main() {
    UserValidator validator;
    UserStorage storage;

    std::string username = "admin";
    std::string password = "123456";

    if (validator.validate(username, password)) {
        storage.save(username, password);
    } else {
        std::cout << "Invalid user" << std::endl;
    }
    return 0;
}

注释:

  • UserValidator 类只负责用户验证,通过 validate 方法验证用户名和密码。
  • UserStorage 类只负责用户存储,通过 save 方法存储用户信息。这样,每个类只负责一项职责,符合单一职责原则。

开闭原则

  • 开闭原则(Open/Closed Principle, OCP)
  • 定义:
    软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
  • 解释:
    当需要对系统添加新功能时,应该通过扩展现有代码(例如通过继承、实现接口等方式)而不是修改现有代码来实现。这样可以确保现有代码的稳定性,避免因修改旧代码而引入新的错误。
    例如,在一个图形绘制系统中,有一个 Shape 基类,其派生类有 Circle、Rectangle 等。如果要添加新的形状 Triangle,应该创建一个新的 Triangle 类继承自 Shape,而不是修改 Shape 类的代码。这样,Shape 类及其已有的派生类的代码保持不变,实现了对修改关闭,对扩展开放。
#include <iostream>
#include <memory>
#include <vector>

// 基类 Shape,定义一个绘制形状的抽象接口
class Shape {
public:
    virtual void draw() = 0;
    virtual ~Shape() = default;
};

// 具体的圆形类,实现 Shape 接口
class Circle : public Shape {
public:
    void draw() override {
        std::cout << "Drawing a circle" << std::endl;
    }
};

// 具体的矩形类,实现 Shape 接口
class Rectangle : public Shape {
public:
    void draw() override {
        std::cout << "Drawing a rectangle" << std::endl;
    }
};

// 图形绘制器类,负责绘制各种形状
class ShapeDrawer {
public:
    void drawShapes(const std::vector<std::shared_ptr<Shape>>& shapes) {
        for (const auto& shape : shapes) {
            shape->draw();
        }
    }
};

int main() {
    std::vector<std::shared_ptr<Shape>> shapes;
    shapes.push_back(std::make_shared<Circle>());
    shapes.push_back(std::make_shared<Rectangle>());

    ShapeDrawer drawer;
    drawer.drawShapes(shapes);

    // 新增一个三角形类,扩展功能而不修改现有代码
    class Triangle : public Shape {
    public:
        void draw() override {
            std::cout << "Drawing a triangle" << std::endl;
        }
    };

    shapes.push_back(std::make_shared<Triangle>());
    drawer.drawShapes(shapes);

    return 0;
}

注释:

  • Shape 是一个抽象基类,定义了 draw 接口,CircleRectangle 是其具体实现。
  • ShapeDrawer 类的 drawShapes 方法可以绘制各种形状,通过使用 Shape
    的指针,它可以绘制不同的形状而不关心具体的形状类型。
  • 新增 Triangle 类时,无需修改 ShapeDrawer 类的代码,符合开闭原则。

里氏替换原则

  • 里氏替换原则(Liskov Substitution Principle, LSP)
  • 定义:
    子类型必须能够替换掉它们的父类型,而程序的行为不会发生变化。
  • 解释:
    这意味着派生类应该可以在任何使用基类的地方被使用,并且不会破坏程序的正确性。派生类在重写基类的方法时,不能改变基类方法的前置条件和后置条件,不能改变基类方法的输入输出约定。
    例如,如果 Bird 是一个基类,PenguinBird 的一个派生类,那么在使用 Bird 的地方,使用 Penguin 替换时,不应该出现错误。如果 Bird 类有一个fly 方法,而 Penguin 不能飞,那么在 Penguin 类中不应该简单地重写 fly 方法为抛出异常或不做任何事情,而应该重新考虑类的继承关系或方法的设计,因为这违反了里氏替换原则。
#include <iostream>

// 基类 Bird,定义一个飞行接口
class Bird {
public:
    virtual void fly() = 0;
    virtual ~Bird() = default;
};

// 能正常飞行的鸟
class Sparrow : public Bird {
public:
    void fly() override {
        std::cout << "Sparrow is flying" << std::endl;
    }
};

// 不会飞行的鸟,重新设计接口以符合 LSP
class Penguin {
public:
    void swim() {
        std::cout << "Penguin is swimming" << std::endl;
    }
};

// 鸟的操作类,使用 Bird 基类
class BirdOperation {
public:
    void makeBirdFly(Bird* bird) {
        bird->fly();
    }
};

int main() {
    Sparrow sparrow;
    BirdOperation operation;
    operation.makeBirdFly(&sparrow);

    // 企鹅类不继承自 Bird,避免违反 LSP
    Penguin penguin;
    // 不会调用企鹅的 fly 方法,因为企鹅不应该有 fly 接口
    // 如果企鹅继承自 Bird 并抛出异常或不做任何事情,就违反了 LSP
    return 0;
}

注释:

  • Bird 是基类,定义了 fly 接口,SparrowBird 的子类,实现了正常飞行。
  • Penguin 类不继承自 Bird,因为它不能飞行。如果让 Penguin 继承 Bird 并实现 fly
    接口(如抛出异常),就会违反里氏替换原则,这里通过重新设计类结构避免了这个问题。
  • BirdOperation 类使用 Bird 基类,调用 fly 方法。

接口隔离原则

  • 接口隔离原则(Interface Segregation Principle, ISP)
  • 定义:
    客户端不应该依赖它不需要的接口。
  • 解释:
    应该将大而全的接口拆分成多个小的、更具体的接口,使客户端只依赖于它们真正需要的接口,避免强迫客户端实现它们不使用的方法。
    例如,有一个 Worker 接口,包含 workeatsleep 方法。对于一个 RobotWorker 类,它不需要 eatsleep 方法,所以可以将 Worker 接口拆分成 Workable(包含 work)、Eatable(包含 eat)和 Sleepable(包含 sleep)接口,这样 RobotWorker 只需要实现 Workable 接口,而不用实现 Eatable Sleepable 接口。
#include <iostream>

// 可工作的接口
class Workable {
public:
    virtual void work() = 0;
    virtual ~Workable() = default;
};

// 可吃的接口
class Eatable {
public:
    virtual void eat() = 0;
    virtual ~Eatable() = default;
};

// 可睡的接口
class Sleepable {
public:
    virtual void sleep() = 0;
    virtual ~Sleepable() = default;
};

// 人类,实现了 Workable, Eatable, Sleepable 接口
class Human : public Workable, public Eatable, public Sleepable {
public:
    void work() override {
        std::cout << "Human is working" << std::endl;
    }
    void eat() override {
        std::cout << "Human is eating" << std::endl;
    }
    void sleep() override {
        std::cout << "Human is sleeping" << std::endl;
    }
};

// 机器人,只实现 Workable 接口
class Robot : public Workable {
public:
    void work() override {
        std::cout << "Robot is working" << std::endl;
    }
};

int main() {
    Human human;
    human.work();
    human.eat();
    human.sleep();

    Robot robot;
    robot.work();
    // 机器人不需要 eat 和 sleep 功能,避免实现不必要的接口
    return 0;
}

注释:

  • Workable、Eatable 和 Sleepable 是不同的接口,将不同的行为分离。
  • Human 实现了三个接口,因为人类可以工作、吃和睡。
  • Robot 只实现 Workable 接口,因为机器人不需要吃和睡,符合接口隔离原则。

依赖倒置原则

  • 依赖倒置原则(Dependency Inversion Principle, DIP)
  • 定义:
    高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
  • 解释:
    这意味着在软件设计中,应该依赖于抽象接口或抽象类,而不是具体的实现类。通过依赖抽象,可以降低模块之间的耦合度,提高代码的可维护性和可扩展性。
    例如,在一个汽车制造系统中,Car 类(高层模块)不应该直接依赖于具体的 Engine 实现类,而是依赖于一个抽象的 IEngine 接口。不同的 Engine 实现(如 GasolineEngine、ElectricEngine)可以实现 IEngine 接口,Car 类可以使用不同的 IEngine 实现,这样当需要更换引擎时,只需要替换 IEngine 的具体实现,而不需要修改 Car 类。
#include <iostream>

// 抽象引擎接口
class IEngine {
public:
    virtual void start() = 0;
    virtual ~IEngine() = default;
};

// 汽油引擎实现
class GasolineEngine : public IEngine {
public:
    void start() override {
        std::cout << "Gasoline engine starts" << std::endl;
    }
};

// 电动引擎实现
class ElectricEngine : public IEngine {
public:
    void start() override {
        std::cout << "Electric engine starts" << std::endl;
    }
};

// 汽车类,依赖于抽象引擎接口
class Car {
private:
    IEngine* engine;
public:
    Car(IEngine* e) : engine(e) {}
    void start() {
        engine->start();
    }
};

int main() {
    // 使用汽油引擎
    IEngine* gasolineEngine = new GasolineEngine();
    Car car1(gasolineEngine);
    car1.start();

    // 使用电动引擎
    IEngine* electricEngine = new ElectricEngine();
    Car car2(electricEngine);
    car2.start();

    delete gasolineEngine;
    delete electricEngine;
    return 0;
}

注释:

  • IEngine 是抽象引擎接口,GasolineEngineElectricEngine 是具体实现。
  • Car 类依赖于 IEngine 接口,而不是具体的引擎实现。这样可以通过传递不同的引擎实现类给 Car 类,实现了依赖倒置原则。

迪米特法则

  • 迪米特法则(Law of Demeter, LoD),也称为最少知识原则(Least Knowledge Principle)
  • 定义:
    一个对象应该对其他对象有最少的了解,只与直接朋友通信,避免耦合度过高。
  • 解释:
    一个对象的 “朋友” 包括:当前对象本身、作为参数传递进来的对象、当前对象的成员变量、如果成员变量是一个集合,那么集合中的元素、当前对象创建的对象。该原则要求对象之间的通信应该尽可能地少,避免一个对象对另一个对象内部的细节有过多的了解。
    例如,在一个公司系统中,Employee 类不应该直接调用 Department 类的 Manager 类的方法,而是通过 Department 类提供的接口来间接调用,这样 Employee 类只需要和 Department 类通信,而不用深入到 Department类内部的 Manager 类。
#include <iostream>
#include <vector>

class Employee {
private:
    std::string name;
public:
    Employee(const std::string& n) : name(n) {}
    std::string getName() const {
        return name;
    }
};

class Department {
private:
    std::vector<Employee> employees;
public:
    void addEmployee(const Employee& e) {
        employees.push_back(e);
    }
    std::vector<Employee> getEmployees() const {
        return employees;
    }
};

class Company {
private:
    std::vector<Department> departments;
public:
    void addDepartment(const Department& d) {
        departments.push_back(d);
    }
    void printEmployees() {
        for (const auto& department : departments) {
            // 通过 Department 提供的接口获取员工信息,而不是直接访问 Department 内部的 Employee 集合
            for (const auto& employee : department.getEmployees()) {
                std::cout << employee.getName() << std::endl;
            }
        }
    }
};

int main() {
    Company company;
    Department department;
    department.addEmployee(Employee("Alice"));
    department.addEmployee(Employee("Bob"));
    company.addDepartment(department);
    company.printEmployees();
    return 0;
}

注释:

  • Employee 类表示员工,包含员工的基本信息。
  • Department 类管理员工,包含添加和获取员工的方法。
  • Company 类管理部门,通过 DepartmentgetEmployees 接口获取员工信息,而不是直接操作
    Department 类中的 employees 集合,符合迪米特法则。

标签:std,原则,void,常见,接口,Bird,class,设计,public
From: https://blog.csdn.net/ke_wu/article/details/144730135

相关文章

  • 硬件设计-硬件 EMC 设计规范
    目录引言:常见原因总体概念及考虑布局屏蔽滤波引言:本规范只简绍EMC的主要原则与结论,为硬件工程师们在开发设计中抛砖引玉。电磁干扰的三要素是干扰源、干扰传输途径、干扰接收器。EMC就围绕这些问题进行研究。最基本的干扰抑制技术是屏蔽、滤波、接地。它们主......
  • 在域控(Domain Controller,DC)上做快照是一种用于备份和恢复的常见操作,尤其是在 Active D
    在域控(DomainController,DC)上做快照是一种用于备份和恢复的常见操作,尤其是在ActiveDirectory环境中。通过创建域控的快照,可以在发生故障时快速恢复到快照时的状态。下面是如何在WindowsServer上创建域控的快照的步骤:1.使用 WindowsServer快照功能在WindowsServer上......
  • # 学期(如2024-2025-1) 学号(如:20241402) 《计算机基础与程序设计》第14周学习总结
    学期(如2024-2025-1)学号(如:20241402)《计算机基础与程序设计》第14周学习总结作业信息这个作业属于哪个课程<班级的链接>(如2024-2025-1-计算机基础与程序设计)这个作业要求在哪里<作业要求的链接>(如2024-2025-1计算机基础与程序设计第一周作业)这个作业的目标<写上......
  • 常见字符串算法简记(持续更新中)
    包含KMP(border相关,KMP自动机),Manacher,Zalgorithm(exKMP),SuffixArray的简单记录。当然写的很烂,欢迎当玩笑看。0.前言1.记一忘三二本文所写的字符串算法都基于一个基本思想:充分利用已知信息的性质加速求出所求信息的过程。这是老生常谈的。因此在这些算法的复杂度分析中主要......
  • 基于java的SpringBoot/SSM+Vue+uniapp的小型企业办公自动化系统的详细设计和实现(源码
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • 最短路径优先原则
    扩展配置:负载均衡:当路由器访问同一个目标且具有多条开销相似的路径(传输速度相似)时,可以让设备将流量拆分后延多条路径同时发送,已达到叠加带宽的作用。         人话:在传输数据包时,将多个数据包分多条路径传输  环回接口:路由器配置的虚拟接口,一般用于虚拟......
  • 【系统设计】云时代的BCAS理念
    文章目录BCAS是什么BCAS相关技术微服务架构容器化技术基础设施即代码(IaC)多云管理平台BCAS的挑战BCAS是什么BarebonesCloudAgnosticSite(BCAS),通常译为“简约云无关站点”,或“基础云无关站点”。它的另一个近义词是“多云架构”(hybridmulticloud)。BCAS指......
  • Java基于SpringBoot的小说阅读平台的设计-java vue.js idea
    所需该项目可以在最下面查看联系方式,为防止迷路可以收藏文章,以防后期找不到项目介绍Java基于SpringBoot的小说阅读平台的设计-javavue.jsidea系统实现截图技术栈介绍JDK版本:jdk1.8+编程语言:java框架支持:springboot数据库:mysql版本不限数据库......
  • Java基于SpringBoot的宠物寄领养网站的设计与实现-java vue.js idea
    所需该项目可以在最下面查看联系方式,为防止迷路可以收藏文章,以防后期找不到项目介绍Java基于SpringBoot的宠物寄领养网站的设计与实现-javavue.jsidea系统实现截图技术栈介绍JDK版本:jdk1.8+编程语言:java框架支持:springboot数据库:mysql版本......
  • “移动家政”:响应式设计在家政服务平台中的应用
    2系统开发技术对系统的开发需要做好准备工作,其中安装开发的工具以及学习开发中需要运用的技术都是需要提前进行的,本节内容就对开发中运用的工具还有技术进行阐述。2.1MySQL数据库本设计用到的数据库就是MySQL数据库,之所以用到这个数据库的原因很多。首先,从满足功能需求......