SOLID软件设计原则 解析

前言:在平时coding过程中,大部分程序员可能把更多精力和时间花在功能的实现和完成上面,对于代码的可读性、可读性及可扩展性没有过多的关注,这可能会造成后期功能扩展困难、新人无法理解等问题。这里介绍一些软件代码设计原则,帮助大家提升代码质量。

目录

一、SOLID软件设计原则

[二、单一职责原则(Single Responsible Principle)](#二、单一职责原则(Single Responsible Principle))

[二、开闭原则(Open Close Principle)](#二、开闭原则(Open Close Principle))

[三、里氏替换原则(Liskov Substitution Principle)](#三、里氏替换原则(Liskov Substitution Principle))

[四、接口隔离原则(Interface Segregation Principle)](#四、接口隔离原则(Interface Segregation Principle))

[五、依赖倒置原则(Dependency Inversion Principle)](#五、依赖倒置原则(Dependency Inversion Principle))


一、SOLID软件设计原则

SOLID是面向对象软件设计中5个基础设计原则的简写,由Robert C. Martin提出,是设计模式的指导思想。这些原则包括单一职责原则、开闭原则、里氏替换原则、接口隔离原则、依赖倒置原则。下面章节分别展开介绍。

二、单一职责原则(Single Responsible Principle)

1)定义:一个类应当只做一件事情,只有一个引起它变化的原因

2)思想:避免一个类同时负责多个功能,否则对一个功能的修改可能会影响到其他功能,修改扩展成本增加。

3)示例

反例:一个**UserManager** 类同时负责用户信息管理用户日志记录,如果日志的存储方式修改(比如从本地文件改成数据库),就需要修改这个类,违反了单一职责。

正例:拆分为两个类:

cpp 复制代码
// 只负责用户信息的管理
class UserManager {
public:
    void addUser(const std::string& username) {
        // 处理用户添加的逻辑
    }
    void deleteUser(const std::string& username) {
        // 处理用户删除的逻辑
    }
};

// 只负责用户相关的日志记录
class UserLogger {
public:
    void logUserOperation(const std::string& username, const std::string& operation) {
        // 处理日志记录的逻辑
    }
};

二、开闭原则(Open Close Principle)

1)定义:软件模块(类、函数等)应该对扩展开放,对修改关闭。

2)思想:当需要新增功能时,应当通过扩展已有代码模块来实现,而不是在已有代码上直接修改、打补丁,防止补丁上打补丁,时间长了就难以维护了。

3)示例

反例:一个**ShapeCalculator**类,计算不同图形的面积,如果新增一种图形(比如椭圆),就需要修改类的代码:

cpp 复制代码
// 反例:新增图形需要修改这个类
class ShapeCalculator {
public:
    double calculateArea(const std::string& shapeType, double param1, double param2) {
        if (shapeType == "circle") {
            return M_PI * param1 * param1;
        } else if (shapeType == "rectangle") {
            return param1 * param2;
        }
        // 新增椭圆需要在这里加else if
        return 0;
    }
};

正例:通过抽象类+继承实现

cpp 复制代码
// 抽象的图形类
class Shape {
public:
    virtual double calculateArea() const = 0;
    virtual ~Shape() = default;
};

// 圆形类,继承Shape
class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) {}
    double calculateArea() const override {
        return M_PI * radius * radius;
    }
};

// 矩形类,继承Shape
class Rectangle : public Shape {
private:
    double width;
    double height;
};

// 面积计算器,只依赖抽象类,新增图形不需要修改这个类
class ShapeCalculator {
public:
    double calculateArea(const Shape& shape) {
        return shape.calculateArea();
    }
};

上述代码中出现了虚函数和抽象类的概念,如需要可参考之前的文章:C++ 虚函数 解析指南-CSDN博客

三、里氏替换原则(Liskov Substitution Principle)

1)定义:子类对象可以替换父类对象在程序中的所有使用场景,且不会改变程序的正确性。

2)思想:子类必须完全实现父类功能,不能破坏父类的行为,避免继承关系的滥用。

3)示例

反例:正方形继承矩形,但是正方形的宽和高必须相等,修改宽的时候高也会变化,破坏了矩形的行为

cpp 复制代码
class Rectangle {
protected:
    double width;
    double height;
public:
    void setWidth(double w) { width = w; }
    void setHeight(double h) { height = h; }
    double getArea() { return width * height; }
};

// 反例:正方形继承矩形,但是setWidth和setHeight的行为不符合父类的约定
class Square : public Rectangle {
public:
    void setWidth(double w) override {
        width = w;
        height = w;
    }
    void setHeight(double h) override {
        width = h;
        height = h;
    }
};

正例:重新设计继承关系,使得正方形与矩形都继承抽象的Shape类。

四、接口隔离原则(Interface Segregation Principle)

1)定义:客户端不应当依赖它不需要的接口,一个类对另一个类的依赖应当建立在最小接口上。

2)思想:避免设计大而全的接口,接口应当拆分多个小的,高内聚、低耦合。

3)示例

反例:一个大的Worker接口,包含了所有工作相关的方法,但是不同的 worker 只需要其中一部分

cpp 复制代码
// 反例:大而全的接口
class Worker {
public:
    virtual void work() = 0;
    virtual void eat() = 0;
    virtual void sleep() = 0;
};

// 机器人只需要work,但是必须实现eat和sleep
class Robot : public Worker {
public:
    void work() override { /* 工作逻辑 */ }
    void eat() override { /* 机器人不需要吃饭,空实现 */ }
    void sleep() override { /* 机器人不需要睡觉,空实现 */ }
};

正例:拆分为小接口

cpp 复制代码
// 只包含工作的接口
class Workable {
public:
    virtual void work() = 0;
    virtual ~Workable() = default;
};

// 只包含休息相关的接口
class Restable {
public:
    virtual void eat() = 0;
    virtual void sleep() = 0;
    virtual ~Restable() = default;
};

// 机器人只实现Workable接口
class Robot : public Workable {
public:
    void work() override { /* 工作逻辑 */ }
};

// 人类实现Workable和Restable接口
class HumanWorker : public Workable, public Restable {
public:
    void work() override { /* 工作逻辑 */ }
    void eat() override { /* 吃饭逻辑 */ }
    void sleep() override { /* 睡觉逻辑 */ }
};

五、依赖倒置原则(Dependency Inversion Principle)

1)定义:上层模块不应当依赖底层模块抽象不应当依赖细节,细节应当依赖抽象。

2)思想:通过抽象(抽象类或接口)实现上层与底层解耦,使得模块之间依赖关系倒置,提高灵活性。

3)示例

反例:上层模块ReportGenerator 直接依赖底层模块MySQLDatabase ,如果需要切换数据库(比如改成 PostgreSQL ),就需要修改ReportGenerator

cpp 复制代码
// 反例:上层依赖底层的具体实现
class MySQLDatabase {
public:
    std::string getData() {
        return "从MySQL获取的数据";
    }
};

class ReportGenerator {
private:
    MySQLDatabase db;
public:
    std::string generateReport() {
        return "报告内容:" + db.getData();
    }
};

正例:通过抽象接口解耦

cpp 复制代码
// 抽象的数据库接口
class Database {
public:
    virtual std::string getData() const = 0;
    virtual ~Database() = default;
};

// MySQL的具体实现,依赖抽象接口
class MySQLDatabase : public Database {
public:
    std::string getData() const override {
        return "从MySQL获取的数据";
    }
};

// PostgreSQL的具体实现,依赖抽象接口
class PostgreSQLDatabase : public Database {
public:
    std::string getData() const override {
        return "从PostgreSQL获取的数据";
    }
};

// 高层模块依赖抽象接口,而不是具体实现
class ReportGenerator {
private:
    const Database& db;
public:
    // 通过构造函数注入依赖
    ReportGenerator(const Database& database) : db(database) {}
    std::string generateReport() {
        return "报告内容:" + db.getData();
    }
};

结束语:一般这5种原则不会孤立存在,通常两三个会结合在一起使用。读者可参考进行理解消化,最终目的是应用于实际项目中,实现代码质量显著提升。

相关推荐
TDengine (老段)8 小时前
开放生态破局工业大数据困局:TDengine 的迭代升级与全链路数据自由流动
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
一位代码8 小时前
一些常用的通用 mysql 命令详解及注意事项
数据库·mysql
曹牧8 小时前
Oracle:五笔码
数据库·oracle
今晚务必早点睡8 小时前
MySQL 新手避坑指南:安装、区分、检查一步到位
数据库·mysql·adb
JIngJaneIL8 小时前
基于java+ vue畅游游戏销售管理系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot·游戏
詹姆斯爱研究Java8 小时前
基于Django的租房网站的设计与实现
数据库·python·django
deng-c-f8 小时前
Linux C/C++ 学习日记(50):连接池
数据库·学习·连接池
运维行者_9 小时前
APM 性能监控是什么?从应用监控与网站监控了解基础概念
网络·数据库·云原生·容器·kubernetes·智能路由器·运维开发
全栈小59 小时前
【数据库】当InfluxDB遇到天花板:金仓数据库如何重构时序性能极限?
数据库·重构