C++设计模式几大基本设计原则

一、单一职责原则(SRP)

1、概念

单一职责原则是一种编程原则,要求一个类应该只有一个引发其变化的原因。换句话说,一个类应该只负责一项职责

2、为什么重要
  • 可维护性:遵循单一职责原则的代码更容易维护,因为每个类都有明确的职责。
  • 可读性:代码结构更清晰,每个类的功能更易于理解。
  • 可扩展性:当需要添加新功能或修改现有功能时,遵循这一原则可以减少对其他部分代码产生影响的可能性。
3、如何实现
  • 在设计类的时候,仔细考虑其职责,并尽量确保每个类只做一件事。
  • 如果发现一个类有多重职责,考虑将其分解成多个只有单一职责的类。
4、C++代码示例

下面的代码展示一个违反单一职责原则的例子和其修正版本。

违反SRP原则的代码

#include <iostream>
#include <string>
#include <fstream>

class Report {
public:
    Report(const std::string& title) : title(title) {}
    
    void generate() {
        std::cout << "Generating report with title: " << title << std::endl;
        // ...其他逻辑
        saveToFile();
    }
    
    void saveToFile() {
        std::ofstream file("report.txt");
        if (file.is_open()) {
            file << "Report title: " << title << std::endl;
            // ...其他内容
            file.close();
        }
    }

private:
    std::string title;
};

int main() {
    Report report("Monthly Sales");
    report.generate();
    return 0;
}

代码中,Report类有两个不同的职责:

  1. 报告生成(Generating Report):该类负责生成报告,这通常涉及到格式化数据、应用业务逻辑等。

  2. 文件保存(Saving to File):该类还负责将生成的报告保存到磁盘上的一个文件中。

这两个职责应当是分开的,下面是正确的写法,Report类只负责报告的生成,而ReportSaver类负责将报告保存到文件。这样,每个类都只有一个职责,遵循了单一职责原则。

#include <iostream>
#include <string>
#include <fstream>

class Report {
public:
    Report(const std::string& title) : title(title) {}
    
    void generate() {
        std::cout << "Generating report with title: " << title << std::endl;
        // ...其他逻辑
    }

private:
    std::string title;
};

class ReportSaver {
public:
    static void saveToFile(const Report& report) {
        std::ofstream file("report.txt");
        if (file.is_open()) {
            file << "Report content..." << std::endl;
            // ...其他内容
            file.close();
        }
    }
};

int main() {
    Report report("Monthly Sales");
    report.generate();
    ReportSaver::saveToFile(report);
    return 0;
}

二、开放封闭原则( OCP)

1、原则概述

开放封闭原则是面向对象设计原则之一,它指出软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着你的代码应该允许在不修改现有代码的情况下添加新功能。

2、结构
  1. 定义

    • 对扩展开放:新的功能应该通过添加新代码来实现。
    • 对修改封闭:一旦一个模块的行为被确定,应该避免修改它。
  2. 目的

    • 提高代码的可维护性。
    • 提高代码的可复用性。
    • 增加代码的健壮性。
  3. 应用场景

    • 需要添加新功能或变更需求时。
    • 多人或多团队并行开发时,为减少彼此之间的依赖。
  4. 实现方式

    • 使用抽象类和接口。
    • 使用装饰器模式、策略模式等设计模式。
3、代码示例

假设有一个绘图程序,需要绘制不同形状。如果不使用开放封闭原则,代码如下:

enum ShapeType { Circle, Square };

class Shape {
public:
    ShapeType type;
};

class Circle : public Shape {
public:
    Circle() { type = Circle; }
};

class Square : public Shape {
public:
    Square() { type = Square; }
};

void drawShape(Shape* shape) {
    if (shape->type == Circle) {
        // 绘制圆形
    } else if (shape->type == Square) {
        // 绘制正方形
    }
}

这种设计违反了开放封闭原则,因为每当添加一个新的形状,都必须修改drawShape函数。

现在,我们用开放封闭原则来改进这个设计:

class Shape {
public:
    virtual void draw() = 0;  // 抽象方法
};

class Circle : public Shape {
public:
    void draw() override {
        // 绘制圆形
    }
};

class Square : public Shape {
public:
    void draw() override {
        // 绘制正方形
    }
};

void drawShape(Shape* shape) {
    shape->draw();  // 现在是对扩展开放的
}

在这个新设计中,每当需要添加一个新的形状,只需继承Shape类并实现draw方法。无需修改现有的drawShape函数或其他代码。

这样,我们就成功地遵守了开放封闭原则。这使得代码更易于维护和扩展,并降低了出错的可能性。

三、依赖倒转原则( DIP)

1、原则概述

依赖倒转原则主张高层模块不应该依赖低层模块,两者都应该依赖其抽象。

2、结构

先解释一下一些概念:

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

    • 高层模块不应该依赖低层模块。两者都应该依赖于抽象。
    • 抽象不应该依赖于细节。细节应该依赖于抽象(子类重写父类虚函数)。
  2. 目的

    • 解耦,提高系统的可维护性和可扩展性。
    • 增强模块之间的可替换性。
  3. 应用场景

    • 当需要改变系统中的数据库、框架或者库时。
    • 当需要构建多平台应用时。
    • 当系统需要拥有很高的可测试性时。
  4. 实现方式

    • 使用抽象类或接口。
    • 使用依赖注入。
3、场景举例

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

大聪明的团队在设计系统时没有遵循依赖倒转原则,导致高层(业务逻辑)直接依赖于低层(数据库操作)的具体实现。因此,当底层数据库从MySQL变更为Oracle时,高层代码也需要相应地修改。

正确的做法:

1.定义数据库操作的抽象接口:在系统中定义一个数据库操作的抽象接口,这个接口提供一组通用的数据库操作方法,如增加(add)、删除(delete)、查找(find)和更新(update)。

class IDatabase {
public:
    virtual void add() = 0;
    virtual void delete() = 0;
    virtual void find() = 0;
    virtual void update() = 0;
};

2.实现抽象接口:MySQL和Oracle各自实现这个抽象接口。

class MySQLDatabase : public IDatabase {
    // 实现 IDatabase 的方法
    void add() override { /* ... */ }
    void delete() override { /* ... */ }
    void find() override { /* ... */ }
    void update() override { /* ... */ }
};

class OracleDatabase : public IDatabase {
    // 实现 IDatabase 的方法
    void add() override { /* ... */ }
    void delete() override { /* ... */ }
    void find() override { /* ... */ }
    void update() override { /* ... */ }
};

3.高层依赖于抽象:业务层只依赖于IDatabase接口,并不关心具体是什么数据库。

class BusinessLogic {
private:
    IDatabase* database;
public:
    BusinessLogic(IDatabase* db) : database(db) {}
    void doSomething() {
        database->add();
        // ...
    }
};

4.使用依赖注入:在运行时,将具体的数据库实现(MySQL或Oracle)注入到业务逻辑中。

int main() {
    //回顾C++语法的同族类型转换的向上转换
    IDatabase* db = new OracleDatabase(); // 或者 new MySQLDatabase();
    BusinessLogic bl(db);
    bl.doSomething();
    delete db;
    return 0;
}

通过这种方式,即使底层数据库更改,高层代码(BusinessLogic)也不需要修改。只需要在运行时注入新的数据库实现即可,实现了真正的解耦。这就是依赖倒转原则的应用。

4、里氏代换原则

依赖倒转原则的前提是需要先满足里氏代换原则:

这个原则是面向对象设计的基本原则之一,主要用于指导类的继承和多态。里氏代换原则强调,子类型必须能够替换它们的基类型,而不影响程序的正确性。

换句话说,如果ST的子类型,那么在不改变程序正确性的前提下,可以用S的对象替换T的对象。

原则内容

  1. 方法覆写规则:子类可以实现父类的抽象方法,但不能覆写(override)父类的非抽象(已实现)方法。
  2. 数据约束扩展:子类可以增加数据属性和方法,但不能减少父类定义好的数据属性和方法。
  3. 不破坏类型不变量:子类维持父类已定义的类型不变性。
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值