MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C++中的设计模式应用

2024-04-081.9k 阅读

设计模式概述

设计模式是指在软件开发过程中,针对反复出现的问题所总结归纳出的通用解决方案。这些模式就像是建筑领域中的经典建筑结构,能帮助我们构建出更加健壮、可维护、可扩展的软件系统。设计模式并非是某种特定语言独有的,而是一种通用的软件设计思想,C++作为一门强大的面向对象编程语言,为实现这些设计模式提供了丰富的语法和特性支持。

设计模式的分类

设计模式主要分为创建型、结构型和行为型三大类。创建型模式主要用于对象的创建过程,比如如何实例化对象,控制对象创建的数量等;结构型模式关注的是如何将类或对象组合成更大的结构;行为型模式则侧重于处理对象之间的交互和职责分配。

  1. 创建型模式
    • 单例模式:保证一个类仅有一个实例,并提供一个全局访问点。在 C++ 中实现单例模式,常见的方式有饿汉式和懒汉式。 饿汉式在程序启动时就创建实例,代码如下:
class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton instance;
        return instance;
    }
    // 禁止拷贝构造和赋值运算符
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
private:
    Singleton() = default;
    ~Singleton() = default;
};

懒汉式则是在第一次使用时才创建实例,代码如下:

class Singleton {
public:
    static Singleton& getInstance() {
        static std::once_flag flag;
        static Singleton* instance;
        std::call_once(flag, []() {
            instance = new Singleton();
        });
        return *instance;
    }
    // 禁止拷贝构造和赋值运算符
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
private:
    Singleton() = default;
    ~Singleton() = default;
};
- **工厂模式**:包括简单工厂、工厂方法和抽象工厂。简单工厂模式是由一个工厂类根据传入的参数决定创建出哪一种产品类的实例。例如:
class Product {
public:
    virtual void show() = 0;
    virtual ~Product() = default;
};
class ConcreteProductA : public Product {
public:
    void show() override {
        std::cout << "This is ConcreteProductA" << std::endl;
    }
};
class ConcreteProductB : public Product {
public:
    void show() override {
        std::cout << "This is ConcreteProductB" << std::endl;
    }
};
class SimpleFactory {
public:
    Product* createProduct(int type) {
        if (type == 1) {
            return new ConcreteProductA();
        } else if (type == 2) {
            return new ConcreteProductB();
        }
        return nullptr;
    }
};

工厂方法模式将工厂类的创建方法抽象成抽象方法,由具体的子类来实现创建不同的产品。

class Product {
public:
    virtual void show() = 0;
    virtual ~Product() = default;
};
class ConcreteProductA : public Product {
public:
    void show() override {
        std::cout << "This is ConcreteProductA" << std::endl;
    }
};
class ConcreteProductB : public Product {
public:
    void show() override {
        std::cout << "This is ConcreteProductB" << std::endl;
    }
};
class Factory {
public:
    virtual Product* createProduct() = 0;
    virtual ~Factory() = default;
};
class ConcreteFactoryA : public Factory {
public:
    Product* createProduct() override {
        return new ConcreteProductA();
    }
};
class ConcreteFactoryB : public Factory {
public:
    Product* createProduct() override {
        return new ConcreteProductB();
    }
};

抽象工厂模式则是创建一系列相关或相互依赖对象的工厂,而无需指定它们具体的类。

  1. 结构型模式
    • 代理模式:为其他对象提供一种代理以控制对这个对象的访问。例如,在网络访问中,我们可以使用代理模式来控制对远程资源的访问。
class RealSubject {
public:
    void request() {
        std::cout << "RealSubject: Handling request." << std::endl;
    }
};
class Proxy {
private:
    RealSubject* realSubject;
public:
    Proxy() : realSubject(nullptr) {}
    ~Proxy() {
        delete realSubject;
    }
    void request() {
        if (!realSubject) {
            realSubject = new RealSubject();
        }
        std::cout << "Proxy: Pre - processing before request." << std::endl;
        realSubject->request();
        std::cout << "Proxy: Post - processing after request." << std::endl;
    }
};
- **装饰器模式**:动态地给一个对象添加一些额外的职责。就像给一个蛋糕添加不同的装饰一样。例如:
class Component {
public:
    virtual void operation() = 0;
    virtual ~Component() = default;
};
class ConcreteComponent : public Component {
public:
    void operation() override {
        std::cout << "ConcreteComponent: Performing basic operation." << std::endl;
    }
};
class Decorator : public Component {
protected:
    Component* component;
public:
    Decorator(Component* comp) : component(comp) {}
    void operation() override {
        component->operation();
    }
};
class ConcreteDecoratorA : public Decorator {
public:
    ConcreteDecoratorA(Component* comp) : Decorator(comp) {}
    void operation() override {
        std::cout << "ConcreteDecoratorA: Adding additional behavior before." << std::endl;
        Decorator::operation();
        std::cout << "ConcreteDecoratorA: Adding additional behavior after." << std::endl;
    }
};
  1. 行为型模式
    • 观察者模式:定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当这个主题对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。例如:
#include <vector>
#include <iostream>
class Observer;
class Subject {
private:
    std::vector<Observer*> observers;
    int state;
public:
    void attach(Observer* observer) {
        observers.push_back(observer);
    }
    void detach(Observer* observer) {
        for (auto it = observers.begin(); it != observers.end(); ++it) {
            if (*it == observer) {
                observers.erase(it);
                break;
            }
        }
    }
    void notify() {
        for (Observer* observer : observers) {
            observer->update(this);
        }
    }
    int getState() const {
        return state;
    }
    void setState(int newState) {
        state = newState;
        notify();
    }
};
class Observer {
public:
    virtual void update(Subject* subject) = 0;
    virtual ~Observer() = default;
};
class ConcreteObserver : public Observer {
private:
    std::string name;
public:
    ConcreteObserver(const std::string& n) : name(n) {}
    void update(Subject* subject) override {
        std::cout << "Observer " << name << " updated. New state: " << subject->getState() << std::endl;
    }
};
- **策略模式**:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。例如:
class Strategy {
public:
    virtual void execute() = 0;
    virtual ~Strategy() = default;
};
class ConcreteStrategyA : public Strategy {
public:
    void execute() override {
        std::cout << "ConcreteStrategyA: Executing algorithm A." << std::endl;
    }
};
class ConcreteStrategyB : public Strategy {
public:
    void execute() override {
        std::cout << "ConcreteStrategyB: Executing algorithm B." << std::endl;
    }
};
class Context {
private:
    Strategy* strategy;
public:
    Context(Strategy* strat) : strategy(strat) {}
    ~Context() {
        delete strategy;
    }
    void setStrategy(Strategy* strat) {
        delete strategy;
        strategy = strat;
    }
    void executeStrategy() {
        strategy->execute();
    }
};

C++ 特性对设计模式实现的支持

面向对象特性

  1. 封装:C++ 的封装特性通过类来实现,将数据和操作数据的方法封装在一起。在设计模式中,这一特性非常重要。例如在单例模式中,我们将实例的创建和管理封装在类内部,通过静态方法提供外部访问点,同时禁止外部对实例进行拷贝和赋值,保证了单例的唯一性。
  2. 继承:继承允许一个类继承另一个类的属性和方法,实现代码的复用。在工厂模式中,具体产品类继承自抽象产品类,具体工厂类继承自抽象工厂类,通过继承关系实现了不同产品和工厂的共性抽取,使得代码结构更加清晰,易于维护和扩展。
  3. 多态:C++ 支持编译时多态(函数重载和模板)和运行时多态(虚函数和指针/引用)。在观察者模式中,通过虚函数实现了运行时多态。当主题对象状态改变通知观察者时,根据具体观察者对象的类型调用相应的 update 方法,实现了不同观察者的不同行为。

模板和泛型编程

模板是 C++ 强大的特性之一,它支持泛型编程。在设计模式中,模板可以用于增强模式的通用性。例如,在实现一个通用的单例模板类时,可以这样写:

template<typename T>
class Singleton {
public:
    static T& getInstance() {
        static T instance;
        return instance;
    }
    // 禁止拷贝构造和赋值运算符
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
private:
    Singleton() = default;
    ~Singleton() = default;
};

这样,通过模板可以为不同类型创建单例实例,提高了代码的复用性。在工厂模式中,也可以使用模板来创建通用的工厂类,使得工厂可以创建不同类型的产品,而不需要为每种产品都写一个具体的工厂类。

智能指针

智能指针是 C++ 用于管理动态内存的工具,在设计模式实现中,智能指针可以有效地解决内存管理问题。例如在代理模式中,如果代理类持有对真实对象的指针,使用智能指针可以避免内存泄漏。以 std::unique_ptr 为例:

class RealSubject {
public:
    void request() {
        std::cout << "RealSubject: Handling request." << std::endl;
    }
};
class Proxy {
private:
    std::unique_ptr<RealSubject> realSubject;
public:
    Proxy() {}
    void request() {
        if (!realSubject) {
            realSubject = std::make_unique<RealSubject>();
        }
        std::cout << "Proxy: Pre - processing before request." << std::endl;
        realSubject->request();
        std::cout << "Proxy: Post - processing after request." << std::endl;
    }
};

当 Proxy 对象销毁时,std::unique_ptr 会自动释放 RealSubject 对象的内存,无需手动管理。

设计模式在实际项目中的应用场景

游戏开发

  1. 状态模式:在游戏角色的状态管理中非常有用。例如,一个游戏角色可能有正常状态、奔跑状态、跳跃状态等。通过状态模式,可以将每个状态的行为封装成独立的类,当角色状态发生变化时,只需要切换状态对象即可。例如:
class Character;
class State {
public:
    virtual void handleInput(Character& character, int input) = 0;
    virtual void update(Character& character) = 0;
    virtual ~State() = default;
};
class NormalState : public State {
public:
    void handleInput(Character& character, int input) override {
        if (input == 1) {
            character.setState(std::make_unique<RunningState>());
        } else if (input == 2) {
            character.setState(std::make_unique<JumpingState>());
        }
    }
    void update(Character& character) override {
        std::cout << "Character is in normal state." << std::endl;
    }
};
class RunningState : public State {
public:
    void handleInput(Character& character, int input) override {
        if (input == 0) {
            character.setState(std::make_unique<NormalState>());
        }
    }
    void update(Character& character) override {
        std::cout << "Character is running." << std::endl;
    }
};
class Character {
private:
    std::unique_ptr<State> state;
public:
    Character() : state(std::make_unique<NormalState>()) {}
    void setState(std::unique_ptr<State> newState) {
        state = std::move(newState);
    }
    void handleInput(int input) {
        state->handleInput(*this, input);
    }
    void update() {
        state->update(*this);
    }
};
  1. 观察者模式:用于游戏中的事件系统。例如,当玩家完成一个任务时,可能需要通知多个系统,如任务系统、成就系统、奖励系统等。通过观察者模式,这些系统可以作为观察者监听任务完成这一主题事件,当事件发生时,自动执行相应的操作。

图形界面开发

  1. MVC(Model - View - Controller)模式:虽然严格来说 MVC 不是标准的设计模式,但它是一种架构模式,其实现中融合了多种设计模式思想。在 C++ 的图形界面开发库如 Qt 中,MVC 模式被广泛应用。Model 表示数据模型,View 负责数据的显示,Controller 则处理用户输入并协调 Model 和 View。例如,在一个简单的文本编辑器中,文本数据是 Model,文本显示区域是 View,而处理用户键盘输入和菜单操作的部分就是 Controller。
  2. 装饰器模式:可以用于给图形界面组件添加额外的功能。比如给一个按钮添加动画效果、提示信息等。通过装饰器模式,可以在不改变按钮原有类的情况下,动态地添加这些功能。

网络编程

  1. 代理模式:在网络代理服务器的实现中,代理模式是核心。客户端请求通过代理服务器转发到目标服务器,代理服务器可以对请求进行过滤、缓存等操作。例如,在实现一个简单的 HTTP 代理服务器时,代理类可以拦截客户端的 HTTP 请求,根据配置决定是否转发请求,以及是否从缓存中获取响应数据。
  2. 策略模式:在网络通信协议的选择上可以应用策略模式。例如,一个网络应用可能支持 TCP 和 UDP 两种协议,根据不同的需求,如数据可靠性要求、实时性要求等,选择不同的协议策略。通过策略模式,可以将 TCP 和 UDP 协议的实现封装成不同的策略类,在运行时根据具体情况动态切换。

设计模式的选择与权衡

根据需求选择模式

  1. 创建型模式的选择:如果需要严格控制对象的创建过程,保证对象的唯一性,那么单例模式是合适的选择。如果创建对象的逻辑较为复杂,需要根据不同的条件创建不同类型的对象,工厂模式则更为适用。例如,在一个数据库连接池的实现中,如果希望整个应用程序只有一个连接池实例,单例模式是首选;而如果应用程序需要根据不同的数据库类型(如 MySQL、Oracle 等)创建不同的连接对象,工厂模式则更能满足需求。
  2. 结构型模式的选择:当需要在不改变现有类结构的情况下,为对象添加额外的功能,装饰器模式是个好主意。如果需要控制对对象的访问,例如在分布式系统中,对远程对象的访问进行管理,代理模式则更为合适。比如,在一个图片加载库中,如果希望在加载图片时添加缓存功能,装饰器模式可以在不改变图片加载类的基础上实现;而如果图片存储在远程服务器上,需要通过代理服务器进行访问,代理模式就能发挥作用。
  3. 行为型模式的选择:当对象之间存在一对多的依赖关系,并且需要在对象状态改变时通知其他对象,观察者模式是最佳选择。如果有多种算法可供选择,并且希望在运行时能够动态切换算法,策略模式则是合适的。例如,在一个股票交易系统中,当股票价格发生变化时,需要通知多个关注该股票的用户,观察者模式可以实现这一功能;而在交易策略的选择上,根据市场情况动态切换不同的交易策略,策略模式就能满足需求。

权衡设计模式的优缺点

  1. 优点:设计模式提高了软件的可维护性。通过将通用的解决方案应用到软件系统中,使得代码结构更加清晰,模块职责更加明确。例如,在使用观察者模式的系统中,当需要添加新的观察者时,只需要实现相应的接口并注册到主题对象中,而不需要对主题对象的核心代码进行修改。设计模式增强了软件的可扩展性。以工厂模式为例,当需要添加新的产品类型时,只需要创建新的产品类和对应的工厂类,而不需要修改已有的工厂代码。设计模式还提高了软件的复用性,许多设计模式都可以在不同的项目中复用,减少了重复开发的工作量。
  2. 缺点:设计模式可能会增加代码的复杂性。一些复杂的设计模式,如抽象工厂模式,可能需要创建多个类和接口,增加了代码的理解和维护难度。设计模式可能会带来一定的性能开销。例如,在代理模式中,如果代理对象对请求进行了大量的预处理和后处理操作,可能会影响系统的性能。在使用设计模式时,需要根据具体的项目需求和性能要求,权衡其优缺点,合理应用设计模式。

在 C++ 编程中,深入理解和灵活应用设计模式能够极大地提升软件系统的质量和开发效率。通过合理选择和实现设计模式,结合 C++ 丰富的语言特性,可以构建出健壮、可维护、可扩展的软件系统。无论是在大型项目还是小型应用开发中,设计模式都将发挥重要的作用,帮助开发者更好地应对各种软件设计挑战。