【C++】【设计模式的六大原则】

文章目录

  • 一、设计模式的六大原则
    • [1.1单一职责原则(Single Responsibility Principle,SRP):](#1.1单一职责原则(Single Responsibility Principle,SRP):)
    • [1.2 开放封闭原则(Open/Closed Principle,OCP):](#1.2 开放封闭原则(Open/Closed Principle,OCP):)
    • [1.3里氏替换原则(Liskov Substitution Principle,LSP):](#1.3里氏替换原则(Liskov Substitution Principle,LSP):)
    • [1.4 依赖倒置原则(Dependency Inversion Principle,DIP):](#1.4 依赖倒置原则(Dependency Inversion Principle,DIP):)
    • [1.5 接口隔离原则(Interface Segregation Principle,ISP):](#1.5 接口隔离原则(Interface Segregation Principle,ISP):)
    • [1.6 合成复用原则(Composite/Aggregate Reuse Principle,CARP):](#1.6 合成复用原则(Composite/Aggregate Reuse Principle,CARP):)
  • 二、总结

一、设计模式的六大原则

设计模式的六大原则是常见的面向对象设计原则,它们有助于编写可维护、可扩展和易于理解的代码。这些原则包括:

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

一个类应该只有一个引起变化的原因。换句话说,一个类应该只负责一种类型的任务或功能。这样做可以使类更加内聚,并且减少对类的修改造成的影响。

代码示例:

cpp 复制代码
#include <iostream>
#include <string>

// 用户信息类
class UserInfo {
private:
    std::string username;
    std::string email;

public:
    UserInfo(const std::string& username, const std::string& email)
        : username(username), email(email) {}

    // 获取用户名
    std::string getUsername() const {
        return username;
    }

    // 获取邮箱
    std::string getEmail() const {
        return email;
    }
};

// 用户验证类
class UserValidator {
public:
    bool validate(const UserInfo& user) {
        // 对用户进行验证的逻辑...
        return true; // 假设验证通过
    }
};

// 用户通知类
class UserNotifier {
public:
    void notify(const UserInfo& user, const std::string& message) {
        // 向用户发送通知的逻辑...
        std::cout << "Sending notification to " << user.getUsername() << ": " << message << std::endl;
    }
};

int main() {
    // 创建用户信息对象
    UserInfo user("john_doe", "john@example.com");

    // 用户验证
    UserValidator validator;
    if (validator.validate(user)) {
        // 用户验证通过,发送通知
        UserNotifier notifier;
        notifier.notify(user, "Welcome to our system!");
    } else {
        std::cout << "User validation failed!" << std::endl;
    }

    return 0;
}

在这个例子中,我们将 User 类拆分成了 UserInfo、UserValidator 和 UserNotifier 三个类。UserInfo 类负责管理用户信息,UserValidator 类负责验证用户,UserNotifier 类负责向用户发送通知。每个类都只负责一个单一的功能,这样提高了代码的可维护性和可扩展性,并且减少了对类的修改造成的影响。

1.2 开放封闭原则(Open/Closed Principle,OCP):

软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。换句话说,应该通过扩展已有的代码来添加新功能,而不是修改已有的代码。这可以通过抽象和接口实现来实现。
代码示例:

假设我们有一个图形绘制程序,它可以绘制不同形状的图形,如圆形、矩形等。现在我们需要给程序添加一个新的功能,即计算每个图形的面积。按照开放封闭原则的要求,我们应该通过扩展已有的代码来实现这个功能,而不是修改已有的代码。

cpp 复制代码
#include <iostream>
#include <vector>
#include <cmath>

// 抽象图形类
class Shape {
public:
    virtual double area() const = 0; // 计算图形的面积
    virtual void draw() const = 0;   // 绘制图形
    virtual ~Shape() {} // 虚析构函数,确保子类的析构函数被调用
};

// 圆形类
class Circle : public Shape {
private:
    double radius;

public:
    Circle(double r) : radius(r) {}

    double area() const override {
        return M_PI * radius * radius;
    }

    void draw() const override {
        std::cout << "Drawing a circle with radius " << radius << std::endl;
    }
};

// 矩形类
class Rectangle : public Shape {
private:
    double width;
    double height;

public:
    Rectangle(double w, double h) : width(w), height(h) {}

    double area() const override {
        return width * height;
    }

    void draw() const override {
        std::cout << "Drawing a rectangle with width " << width << " and height " << height << std::endl;
    }
};

// 计算图形的总面积
double calculateTotalArea(const std::vector<Shape*>& shapes) {
    double totalArea = 0.0;
    for (const auto& shape : shapes) {
        totalArea += shape->area();
    }
    return totalArea;
}

int main() {
    std::vector<Shape*> shapes;
    shapes.push_back(new Circle(5.0));
    shapes.push_back(new Rectangle(3.0, 4.0));

    // 绘制图形
    for (const auto& shape : shapes) {
        shape->draw();
    }

    // 计算总面积
    double totalArea = calculateTotalArea(shapes);
    std::cout << "Total area of all shapes: " << totalArea << std::endl;

    // 释放内存
    for (const auto& shape : shapes) {
        delete shape;
    }

    return 0;
}

在这个例子中,我们定义了一个抽象基类 Shape,它包含纯虚函数 area() 用于计算图形的面积,以及纯虚函数 draw() 用于绘制图形。然后我们创建了两个具体的图形类 Circle 和 Rectangle,它们分别继承自 Shape 类,并且实现了 area() 和 draw() 函数。通过这种方式,我们可以通过扩展新的图形类来添加新功能,而不需要修改已有的代码。

1.3里氏替换原则(Liskov Substitution Principle,LSP):

子类型必须能够替换其基类型。也就是说,派生类必须能够替换基类并且不会影响程序的正确性。这要求派生类在继承基类时不应该改变基类的行为。
代码示例:

cpp 复制代码
#include <iostream>

// 基类
class Shape {
public:
   virtual double area() const = 0; // 纯虚函数,计算面积
};

// 派生类:矩形
class Rectangle : public Shape {
private:
   double width;
   double height;

public:
   Rectangle(double w, double h) : width(w), height(h) {}

   double area() const override {
       return width * height;
   }
};

// 派生类:正方形
class Square : public Shape {
private:
   double side;

public:
   Square(double s) : side(s) {}

   double area() const override {
       return side * side;
   }
};

// 计算面积的函数
void printArea(const Shape& shape) {
   std::cout << "Area: " << shape.area() << std::endl;
}

int main() {
   Rectangle rect(5.0, 4.0);
   Square square(3.0);

   printArea(rect);   // 输出矩形的面积
   printArea(square); // 输出正方形的面积

   return 0;
}

在这个例子中,Rectangle 和 Square 都是 Shape 的派生类,它们都实现了 area() 函数来计算各自的面积。在 printArea() 函数中,我们通过基类的引用来传递 Rectangle 和 Square 对象,并调用它们的 area() 函数。由于派生类能够替换其基类,因此我们可以将 Rectangle 和 Square 对象传递给 printArea() 函数,而不会影响程序的正确性。

1.4 依赖倒置原则(Dependency Inversion Principle,DIP):

高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。这可以通过依赖注入等技术来实现。
代码示例:

cpp 复制代码
#include <iostream>
#include <string>

// 定义抽象的消息发送接口
class IMessageSender {
public:
   virtual void sendMessage(const std::string& message) const = 0;
};

// 实现具体的邮件发送类
class EmailSender : public IMessageSender {
public:
   void sendMessage(const std::string& message) const override {
       std::cout << "Sending email: " << message << std::endl;
   }
};

// 实现具体的短信发送类
class SMSSender : public IMessageSender {
public:
   void sendMessage(const std::string& message) const override {
       std::cout << "Sending SMS: " << message << std::endl;
   }
};

// 高层模块,依赖于抽象的消息发送接口
class NotificationService {
private:
   const IMessageSender& messageSender;

public:
   NotificationService(const IMessageSender& sender) : messageSender(sender) {}

   void sendNotification(const std::string& message) const {
       messageSender.sendMessage(message);
   }
};

int main() {
   EmailSender emailSender;
   SMSSender smsSender;

   // 使用邮件发送服务
   NotificationService emailService(emailSender);
   emailService.sendNotification("Hello, this is an email notification.");

   std::cout << std::endl;

   // 使用短信发送服务
   NotificationService smsService(smsSender);
   smsService.sendNotification("Hello, this is an SMS notification.");

   return 0;
}

在这个例子中,IMessageSender 是一个抽象的消息发送接口,EmailSender 和 SMSSender 是具体的邮件发送类和短信发送类,它们都实现了 IMessageSender 接口。NotificationService 是一个高层模块,它不直接依赖于具体的邮件发送类或短信发送类,而是依赖于抽象的 IMessageSender 接口。这样做使得 NotificationService 更加灵活,可以轻松地切换不同的消息发送实现,而不需要修改 NotificationService 的代码。

1.5 接口隔离原则(Interface Segregation Principle,ISP):

客户端不应该依赖它不需要的接口。换句话说,应该将大的接口拆分成多个小的接口,以便客户端只需要知道与自己相关的接口。
代码示例:

cpp 复制代码
#include <iostream>

// 定义一个门的接口
class Door {
public:
   virtual void open() = 0;
   virtual void close() = 0;
};

// 定义一个锁的接口
class Lock {
public:
   virtual void lock() = 0;
   virtual void unlock() = 0;
};

// 实现木门
class WoodenDoor : public Door {
public:
   void open() override {
       std::cout << "Wooden door opened." << std::endl;
   }
   void close() override {
       std::cout << "Wooden door closed." << std::endl;
   }
};

// 实现智能锁
class SmartLock : public Lock {
public:
   void lock() override {
       std::cout << "Smart lock locked." << std::endl;
   }
   void unlock() override {
       std::cout << "Smart lock unlocked." << std::endl;
   }
};

// 定义一个安全门,它同时具有门和锁的功能
class SecurityDoor : public Door, public Lock {
public:
   void open() override {
       std::cout << "Security door opened." << std::endl;
   }
   void close() override {
       std::cout << "Security door closed." << std::endl;
   }
   void lock() override {
       std::cout << "Security door locked." << std::endl;
   }
   void unlock() override {
       std::cout << "Security door unlocked." << std::endl;
   }
};

int main() {
   WoodenDoor woodenDoor;
   SmartLock smartLock;
   SecurityDoor securityDoor;

   // 使用木门
   woodenDoor.open();
   woodenDoor.close();

   // 使用智能锁
   smartLock.lock();
   smartLock.unlock();

   // 使用安全门
   securityDoor.open();
   securityDoor.lock();
   securityDoor.unlock();
   securityDoor.close();

   return 0;
}

在这个例子中,我们定义了 Door 和 Lock 两个接口,分别表示门和锁的功能。然后我们实现了 WoodenDoor 类和 SmartLock 类来分别表示木门和智能锁,并且它们分别实现了对应的接口。最后,我们定义了 SecurityDoor 类,它同时继承了 Door 和 Lock 接口,表示一个同时具有门和锁功能的安全门。通过这样的设计,客户端只需要使用与自己相关的接口,不需要依赖于不需要的接口,实现了接口隔离原则。

1.6 合成复用原则(Composite/Aggregate Reuse Principle,CARP):

尽量使用合成/聚合,而不是继承来实现代码复用。通过将多个对象组合成一个更大的对象,可以更灵活地管理对象之间的关系,并且减少了继承带来的耦合。
代码示例:

假设我们有一个 Car 类和一个 Engine 类,Car 类需要使用 Engine 类的功能来驱动汽车。如果我们使用继承来实现这个关系,那么 Car 类将会成为 Engine 类的子类,这样做会导致 Car 类与 Engine 类之间的强耦合,而且如果我们想要更换引擎,可能会导致修改 Car 类的代码。相反,我们可以使用合成复用原则,将 Engine 类作为 Car 类的一个成员对象,使得 Car 类可以通过调用 Engine 对象的方法来驱动汽车。

cpp 复制代码
#include <iostream>

// 引擎类
class Engine {
public:
    void start() {
        std::cout << "Engine started." << std::endl;
    }
    void stop() {
        std::cout << "Engine stopped." << std::endl;
    }
};

// 汽车类
class Car {
private:
    Engine engine; // 引擎对象作为成员

public:
    void start() {
        engine.start(); // 调用引擎对象的方法
        std::cout << "Car started." << std::endl;
    }
    void stop() {
        engine.stop(); // 调用引擎对象的方法
        std::cout << "Car stopped." << std::endl;
    }
};

int main() {
    Car car;
    car.start(); // 启动汽车
    car.stop();  // 停止汽车
    return 0;
}

在这个例子中,Car 类包含了一个 Engine 对象作为成员。通过将 Engine 对象作为 Car 类的一部分,我们可以在 Car 类中调用 Engine 类的方法来启动和停止汽车。这样做使得 Car 类和 Engine 类之间的耦合程度降低,同时也使得代码更加灵活和可维护。

二、总结

书山有路勤为径,学海无涯苦作舟。

相关推荐
etsuyou34 分钟前
js前端this指向规则
开发语言·前端·javascript
shizhenshide36 分钟前
为什么有时候 reCAPTCHA 通过率偏低,常见原因有哪些
开发语言·php·验证码·captcha·recaptcha·ezcaptcha
挂科是不可能出现的37 分钟前
最长连续序列
数据结构·c++·算法
mit6.8241 小时前
[Agent可视化] 配置系统 | 实现AI模型切换 | 热重载机制 | fsnotify库(go)
开发语言·人工智能·golang
友友马1 小时前
『 QT 』QT控件属性全解析 (一)
开发语言·前端·qt
小白学大数据2 小时前
实战:Python爬虫如何模拟登录与维持会话状态
开发语言·爬虫·python
一念&2 小时前
每日一个C语言知识:C 结构体
c语言·开发语言
mjhcsp2 小时前
C++ int 类型深度解析:从底层实现到实战应用
c++·int
锦***林2 小时前
用 Python 写一个自动化办公小助手
开发语言·python·自动化
程序员老舅3 小时前
C++参数传递:值、指针与引用的原理与实战
c++·c/c++·值传递·引用传递·指针传递·参数传递机制