MST

星途 面试题库

面试题:C++ 多态与设计模式的深度结合

以策略模式为例,详细说明 C++ 多态在其中的关键作用。如果要在策略模式的基础上,通过多态实现更灵活的动态策略切换,你会如何设计和实现相关代码?同时分析这种设计可能带来的潜在问题及解决方案。
37.8万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

1. C++ 多态在策略模式中的关键作用

策略模式定义了一系列算法,将每个算法封装起来,使它们可以相互替换。在策略模式中,C++ 多态起到了核心作用,主要体现在以下方面:

  • 解耦算法与使用算法的上下文:通过定义一个抽象的策略基类,具体的策略类继承自该基类并实现各自的算法。上下文类持有一个指向策略基类的指针或引用。这样,上下文与具体策略实现分离,只依赖于抽象策略接口,通过多态机制调用不同具体策略的方法。例如:
// 抽象策略类
class Strategy {
public:
    virtual void execute() = 0;
    virtual ~Strategy() = default;
};

// 具体策略类A
class ConcreteStrategyA : public Strategy {
public:
    void execute() override {
        std::cout << "Executing Strategy A" << std::endl;
    }
};

// 具体策略类B
class ConcreteStrategyB : public Strategy {
public:
    void execute() override {
        std::cout << "Executing Strategy B" << std::endl;
    }
};

// 上下文类
class Context {
private:
    Strategy* strategy;
public:
    Context(Strategy* s) : strategy(s) {}
    ~Context() { delete strategy; }
    void doWork() {
        strategy->execute();
    }
};

这里,Context 类通过 Strategy 指针实现了对不同具体策略的调用,而无需关心具体是哪个策略,实现了算法与上下文的解耦。

2. 实现更灵活的动态策略切换

设计思路

为了实现动态策略切换,需要提供一种机制,让上下文在运行时能够改变其所持有的策略对象。可以通过提供一个接口,允许在运行时设置新的策略。

代码实现

// 抽象策略类
class Strategy {
public:
    virtual void execute() = 0;
    virtual ~Strategy() = default;
};

// 具体策略类A
class ConcreteStrategyA : public Strategy {
public:
    void execute() override {
        std::cout << "Executing Strategy A" << std::endl;
    }
};

// 具体策略类B
class ConcreteStrategyB : public Strategy {
public:
    void execute() override {
        std::cout << "Executing Strategy B" << std::endl;
    }
};

// 上下文类
class Context {
private:
    Strategy* strategy;
public:
    Context(Strategy* s) : strategy(s) {}
    ~Context() { delete strategy; }
    void doWork() {
        strategy->execute();
    }
    void setStrategy(Strategy* newStrategy) {
        delete strategy;
        strategy = newStrategy;
    }
};

main 函数中可以这样使用:

#include <iostream>
int main() {
    Strategy* strategyA = new ConcreteStrategyA();
    Context context(strategyA);
    context.doWork();

    Strategy* strategyB = new ConcreteStrategyB();
    context.setStrategy(strategyB);
    context.doWork();

    delete strategyB;
    return 0;
}

这样,Context 类的对象在运行时可以切换不同的策略。

3. 潜在问题及解决方案

潜在问题

  • 内存管理问题:在动态切换策略时,如果不妥善管理内存,可能会导致内存泄漏。例如,在 Context 类的 setStrategy 方法中,如果没有先释放旧的策略对象,就会造成内存泄漏。
  • 增加复杂性:随着策略数量的增加,代码的维护和理解难度可能会增大。每个具体策略类都需要正确实现抽象策略类的接口,否则可能导致运行时错误。

解决方案

  • 内存管理:可以使用智能指针来管理策略对象的生命周期,从而避免手动内存管理带来的问题。例如,将 Context 类中的 Strategy* 改为 std::unique_ptr<Strategy>
// 上下文类
class Context {
private:
    std::unique_ptr<Strategy> strategy;
public:
    Context(std::unique_ptr<Strategy> s) : strategy(std::move(s)) {}
    void doWork() {
        strategy->execute();
    }
    void setStrategy(std::unique_ptr<Strategy> newStrategy) {
        strategy = std::move(newStrategy);
    }
};

这样,智能指针会自动处理对象的销毁,避免内存泄漏。

  • 代码维护:为了降低代码复杂性,可以通过良好的文档注释、命名规范以及合理的代码组织结构来提高代码的可维护性。例如,将相关的策略类放在同一个命名空间或模块中,并为每个策略类提供清晰的功能说明。同时,可以通过单元测试确保每个策略类的正确性。