C++面向对象设计的五大基本原则

C++ 面向对象设计的五大基本原则,通常指的是 SOLID 原则。虽然 SOLID 最初是在 Java 和其他面向对象语言背景下提出的(由 Robert C. Martin 提出),但它们完全适用于 C++ 这样的多范式、支持面向对象编程的语言。

SOLID 是五个英文单词首字母的缩写,分别代表:

  • S – Single Responsibility Principle (单一职责原则)
  • O – Open/Closed Principle (开闭原则)
  • L – Liskov Substitution Principle (里氏替换原则)
  • I – Interface Segregation Principle (接口隔离原则)
  • D – Dependency Inversion Principle (依赖倒置原则)

1. 单一职责原则(Single Responsibility Principle, SRP)

一个类应该只有一个引起它变化的原因。

换句话说,一个类只应负责一项职责。如果一个类承担了多个职责,那么这些职责就耦合在一起,修改其中一个可能会破坏另一个。

class Report {
public:
    void generateReport() { /* 生成报告内容 */ }
    void printReport() { /* 打印到控制台 */ }
    void saveToFile(const std::string& filename) { /* 保存到文件 */ }
};

这个 Report 类既负责生成报告,又负责输出(打印或保存)。如果将来需要支持 PDF 输出、网络发送等,就需要频繁修改这个类。

改进(遵循 SRP):
class Report {
public:
    std::string getContent() { return "Report content"; }
};

class Printer {
public:
    void print(const std::string& content) { /* 打印逻辑 */ }
};

class FileSaver {
public:
    void save(const std:: string& content, const std::string& filename) { /* 保存逻辑 */ }
};

每个类只做一件事,职责清晰,易于维护和扩展。


2. 开闭原则(Open/Closed Principle, OCP)

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

即:当需求变化时,应通过添加新代码来扩展功能,而不是修改已有代码。

C++ 示例(违反 OCP):
class Shape {
public:
    enum Type { Circle, Square };
    Type type;
};

double area(const Shape& s) {
    if (s.type == Shape::Circle) {
        // 计算圆面积
    } else if (s.type == Shape::Square) {
        // 计算正方形面积
    }
}

每增加一种图形,就要修改 area() 函数,违反 OCP。

改进(遵循 OCP):
class Shape {
public:
    virtual double area() const = 0;
    virtual ~Shape() = default;
};

class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    double area() const override { return 3.14159 * radius * radius; }
};

class Square : public Shape {
    double side;
public:
    Square(double s) : side(s) {}
    double area() const override { return side * side; }
};

// 使用
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(2.0));
shapes.push_back(std::make_unique<Square>(3.0));

for (const auto& shape : shapes) {
    std::cout << shape->area() << std::endl;
}

新增图形只需继承 Shape 并实现 area(),无需修改现有代码。


3. 里氏替换原则(Liskov Substitution Principle, LSP)

子类型必须能够替换它们的基类型而不改变程序的正确性。

也就是说,任何使用基类的地方,都应该能无缝使用派生类,而不会导致错误或异常行为。

C++ 反例(违反 LSP):
class Rectangle {
protected:
    int width, height;
public:
    virtual void setWidth(int w) { width = w; }
    virtual void setHeight(int h) { height = h; }
    virtual int getArea() const { return width * height; }
};

class Square : public Rectangle {
public:
    void setWidth(int w) override { width = height = w; }
    void setHeight(int h) override { width = height = h; }
};

现在考虑这段代码:

void resize(Rectangle& r) {
    r.setWidth(5);
    r.setHeight(4);
    assert(r.getArea() == 20); // 对 Square 会失败!
}

因为 Square 强制宽高相等,所以 resize 在传入 Square 时会出错。这违反了 LSP。

解决方案:

避免用继承建模“是正方形的矩形”这种数学关系。可以使用组合,或者重新设计类层次。


4. 接口隔离原则(Interface Segregation Principle, ISP)

客户端不应被迫依赖它们不使用的接口。

在 C++ 中,“接口”通常指纯虚类(抽象基类)。应将大而全的接口拆分为更小、更具体的接口。

反例(违反 ISP):
class Worker {
public:
    virtual void work() = 0;
    virtual void eat() = 0;
    virtual void sleep() = 0;
};

class Human : public Worker {
    void work() override { /* ... */ }
    void eat() override { /* ... */ }
    void sleep() override { /* ... */ }
};

class Robot : public Worker {
    void work() override { /* ... */ }
    void eat() override { /* 机器人不能吃! */ } // 被迫实现无意义方法
    void sleep() override { /* ... */ }
};
改进(遵循 ISP):
class Workable {
public:
    virtual void work() = 0;
};

class Eatable {
public:
    virtual void eat() = 0;
};

class Sleepable {
public:
    virtual void sleep() = 0;
};

class Human : public Workable, public Eatable, public Sleepable {
    void work() override { /* ... */ }
    void eat() override { /* ... */ }
    void sleep() override { /* ... */ }
};

class Robot : public Workable {
    void work() override { /* ... */ }
};

每个类只实现它真正需要的功能。


5. 依赖倒置原则(Dependency Inversion Principle, DIP)

高层模块不应依赖低层模块,二者都应依赖于抽象。 抽象不应依赖细节,细节应依赖抽象。

核心思想:通过抽象(如接口或抽象基类)解耦高层逻辑与底层实现。

反例(违反 DIP):
class Keyboard {};
class Monitor {};

class Computer {
    Keyboard k;
    Monitor m;
public:
    void start() { /* 使用具体类 */ }
};

Computer 直接依赖具体硬件,难以替换或测试。

改进(遵循 DIP):
class IInputDevice {
public:
    virtual void readInput() = 0;
};

class IOutputDevice {
public:
    virtual void display() = 0;
};

class Keyboard : public IInputDevice {
    void readInput() override { /* ... */ }
};

class Monitor : public IOutputDevice {
    void display() override { /* ... */ }
};

class Computer {
    std::unique_ptr<IInputDevice> input;
    std::unique_ptr<IOutputDevice> output;
public:
    Computer(std::unique_ptr<IInputDevice> in, std::unique_ptr<IOutputDevice> out)
        : input(std::move(in)), output(std::move(out)) {}

    void start() {
        input->readInput();
        output->display();
    }
};

现在 Computer 依赖抽象接口,可以轻松替换输入/输出设备(如换成触摸屏、语音识别等),也便于单元测试(可注入 Mock 对象)。


总结

原则核心思想C++ 实践要点
SRP一个类一个职责拆分类,避免多功能耦合
OCP扩展开放,修改关闭多用抽象基类 + 多态
LSP子类可替换父类继承需符合“行为子类型”
ISP小而专注的接口避免胖接口,使用多重继承拆分
DIP依赖抽象而非具体通过接口/抽象类解耦

这些原则不是教条,而是指导我们写出高内聚、低耦合、易维护、可扩展的 C++ 代码的宝贵经验。在实际项目中,应结合具体场景灵活应用。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值