C++面向对象设计原则:单一职责、开闭原则等

欢迎来到C++面向对象设计原则讲座!

各位朋友,欢迎来到我们今天的讲座!今天我们将一起探讨C++面向对象设计中的几个核心原则:单一职责原则(SRP)开闭原则(OCP)以及其他相关的设计思想。为了让内容更有趣,我会用轻松幽默的方式讲解,并结合代码示例和表格来帮助大家理解。


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

什么是单一职责原则?

简单来说,单一职责原则就是告诉我们:一个类应该只负责一件事情。听起来很简单对吧?但实际上,很多人在写代码时会不自觉地让一个类承担过多的责任。

讲个小故事

假设你正在开发一个餐厅管理系统,其中有一个OrderManager类,它不仅要处理订单,还要打印账单、发送邮件通知客户……这就像一个人既当厨师又当服务员还兼会计,忙得不可开交。

class OrderManager {
public:
    void takeOrder() {
        // 处理订单逻辑
    }

    void printBill() {
        // 打印账单逻辑
    }

    void sendEmailNotification() {
        // 发送邮件逻辑
    }
};

问题来了:如果有一天你需要修改账单打印的格式,或者换一种邮件服务提供商,那你不得不去改动这个OrderManager类,而这种改动可能会引入新的bug。

如何改进?

我们可以将不同的职责拆分到不同的类中,比如:

class OrderProcessor {
public:
    void processOrder() {
        // 专注于处理订单
    }
};

class BillPrinter {
public:
    void printBill() {
        // 专注于打印账单
    }
};

class EmailNotifier {
public:
    void sendEmail() {
        // 专注于发送邮件
    }
};

这样每个类都只做一件事,职责清晰明了,未来维护起来也更加方便。


Part 2: 开闭原则(Open-Closed Principle, OCP)

什么是开闭原则?

开闭原则的核心思想是:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。也就是说,当我们需要新增功能时,尽量不要修改现有的代码,而是通过扩展的方式来实现。

再讲一个小故事

假设你正在开发一个绘图程序,支持绘制不同形状的图形,比如圆形和矩形。你的初始代码可能是这样的:

enum ShapeType { CIRCLE, RECTANGLE };

class Shape {
public:
    virtual void draw() = 0;
};

class Circle : public Shape {
public:
    void draw() override {
        std::cout << "Drawing a circle" << std::endl;
    }
};

class Rectangle : public Shape {
public:
    void draw() override {
        std::cout << "Drawing a rectangle" << std::endl;
    }
};

void drawShape(ShapeType type) {
    if (type == CIRCLE) {
        Circle circle;
        circle.draw();
    } else if (type == RECTANGLE) {
        Rectangle rectangle;
        rectangle.draw();
    }
}

现在,如果你需要支持一个新的形状——三角形,你会怎么做?按照目前的代码结构,你需要修改drawShape函数,添加一个新的if-else分支。但问题是,每次新增一个形状都需要修改现有代码,违反了开闭原则。

如何改进?

我们可以使用多态和工厂模式来解决这个问题:

class Triangle : public Shape {
public:
    void draw() override {
        std::cout << "Drawing a triangle" << std::endl;
    }
};

Shape* createShape(ShapeType type) {
    switch (type) {
        case CIRCLE: return new Circle();
        case RECTANGLE: return new Rectangle();
        case TRIANGLE: return new Triangle(); // 新增形状无需修改现有代码
        default: return nullptr;
    }
}

void drawShape(ShapeType type) {
    Shape* shape = createShape(type);
    if (shape) {
        shape->draw();
        delete shape; // 注意释放内存
    }
}

这样,即使未来新增更多的形状,我们也不需要修改drawShape函数,只需要在createShape函数中添加新的分支即可。


Part 3: 其他设计原则小结

除了单一职责原则和开闭原则,还有一些其他重要的设计原则值得了解:

原则名称 描述
里氏替换原则(LSP) 子类必须能够完全替代父类,而不会导致程序出错。
接口隔离原则(ISP) 客户端不应该依赖于它不需要的接口,接口应该尽量小而专一。
依赖倒置原则(DIP) 高层模块不应该依赖于低层模块,两者都应该依赖于抽象。

总结

今天我们学习了两个非常重要的面向对象设计原则:单一职责原则和开闭原则。它们的核心思想是帮助我们写出更易于维护、更灵活的代码。当然,设计原则并不是死板的规则,而是指导我们思考的工具。在实际开发中,我们需要根据具体场景灵活运用。

最后,引用一句来自Robert C. Martin(Uncle Bob)的话:“Clean code is not written by following rules, it is written by thinking about your reader.”(干净的代码不是靠遵循规则写出来的,而是靠为读者着想写出来的。)

感谢大家的聆听!如果有任何问题,欢迎随时提问!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注