You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

运行时切换计算实现的设计模式选型与实践合理性问询

问题背景与疑问

我们有一个通用计算方法Compute,存在多种实现方式,这些实现均继承自IFoo接口,例如FooRed和FooBlue:

class FooRed : public IFoo
{
public:
   FooRed() = default;
   virtual double Compute() override final; // 继承自IFoo
};

原本我们通过std::shared_ptr<IFoo>将同一实现实例分发给多个对象,确保它们使用相同的计算方式,初始化方式为:

std::shared_ptr<IFoo> myFoo = std::make_shared<FooRed>();

并在子对象构造时传递:

SubObject(myFoo);

后续开发中,我们需要支持运行时在FooRed与FooBlue之间切换计算模式,为此编写了如下模板类:

template<class T>
class Toggleable
{
protected:
   std::shared_ptr<T> _instance;
   bool _state;
public:
   Toggleable();
   T* const Get() const { return _instance.get(); }
   const std::shared_ptr<T>& GetRef() const { return _instance; }
   virtual void Toggle(); // 将_instance设置为对应类型
};

以及对应的FooManager类:

class FooManager : public Toggleable<IFoo>
{
public:
    virtual void Toggle() override; // 继承自Toggleable
};

此时UI等模块可通过FooManager的Toggle方法切换模式,子对象通过SubObject(myFoo->GetRef())获取当前实例的引用,确保所有对象使用同一计算方式。

我的初始方案是让FooManager同时实现IFoo接口,类似容器模式:

class FooManager : public Toggleable<IFoo>, public IFoo
{
public:
    virtual void Toggle() override; // 继承自Toggleable
    virtual double Compute() override { return Get()->Compute(); } // 继承自IFoo
};

这种方式无需修改原有构造调用,对底层代码改动极小——所有原本接收IFoo的对象无需感知变化,仅处理切换逻辑的模块需要知晓FooManager或Toggleable类型。此外,这种方式让构造函数参数更统一,避免出现SubObject(myFoo->GetRef(), myBar)这种混合共享指针引用与共享指针的尴尬情况。但同事认为这种多重继承属于不良实践,会混淆管理器与实际Foo的职责。

疑问

  1. 该方案是否为良好实践?
  2. 如何正确实现这种需要运行时切换多子元素的需求?
  3. 当前的实现属于何种设计模式,这种运行时选择方式是否合理?

解答

1. 你的多重继承方案算不算良好实践?

不算绝对的“不良实践”,但确实存在职责混淆的风险——FooManager同时承担了**实例管理(切换、持有)和业务计算(Compute)**两个职责。如果后续IFoo扩展更多接口,你需要在FooManager中逐个转发,代码会变得冗余且维护成本上升。

但它的优势也很明显:最小化代码侵入,原有依赖IFoo的代码完全不需要修改,这在大型项目中是很重要的考量。如果你的IFoo接口稳定、不会频繁扩展,这个方案是可接受的折中方案。

2. 正确实现运行时切换多子元素的思路

有几种更清晰的替代方案,可根据场景选择:

方案一:用代理模式替代多重继承

单独实现一个IFoo的代理类,把切换逻辑和代理逻辑分离:

class FooProxy : public IFoo
{
private:
    std::shared_ptr<IFoo> _currentFoo;
public:
    void SetCurrentFoo(std::shared_ptr<IFoo> foo) { _currentFoo = foo; }
    double Compute() override { return _currentFoo->Compute(); }
};

// 再单独做一个管理器负责切换
class FooManager
{
private:
    std::shared_ptr<FooProxy> _proxy;
    std::shared_ptr<IFoo> _fooRed;
    std::shared_ptr<IFoo> _fooBlue;
    bool _isRed;
public:
    FooManager() : _proxy(std::make_shared<FooProxy>()),
                   _fooRed(std::make_shared<FooRed>()),
                   _fooBlue(std::make_shared<FooBlue>()),
                   _isRed(true)
    {
        _proxy->SetCurrentFoo(_fooRed);
    }

    void Toggle()
    {
        _isRed = !_isRed;
        _proxy->SetCurrentFoo(_isRed ? _fooRed : _fooBlue);
    }

    std::shared_ptr<IFoo> GetFoo() { return _proxy; }
};

这种方式职责完全分离:FooProxy只负责转发请求,FooManager只负责切换和持有实例,原有代码依然可以直接接收GetFoo()返回的IFoo指针,同样不需要修改。

方案二:让子对象依赖管理器而非直接依赖IFoo

如果允许修改子对象的构造逻辑,可以让SubObject直接接收FooManager,内部通过GetRef()获取当前实例:

class SubObject
{
private:
    std::shared_ptr<FooManager> _manager;
public:
    SubObject(std::shared_ptr<FooManager> manager) : _manager(manager) {}

    void DoSomething()
    {
        double result = _manager->GetRef()->Compute();
        // ...
    }
};

这种方式职责最清晰,但需要修改所有子对象的代码,侵入性较高,适合新项目或重构场景。

方案三:改进你的Toggleable模板,让它自带代理能力

把代理逻辑整合到Toggleable模板中,避免多重继承的职责混淆:

template<class T>
class ToggleableProxy : public T
{
protected:
    std::shared_ptr<T> _instance;
    bool _state;
    // 持有所有可选实例
    std::shared_ptr<T> _instanceA;
    std::shared_ptr<T> _instanceB;
public:
    ToggleableProxy(std::shared_ptr<T> a, std::shared_ptr<T> b) 
        : _instanceA(a), _instanceB(b), _state(true)
    {
        _instance = _instanceA;
    }

    void Toggle() override
    {
        _state = !_state;
        _instance = _state ? _instanceA : _instanceB;
    }

    // 转发所有T的接口,这里以Compute为例
    double Compute() override { return _instance->Compute(); }
};

// 使用方式
auto fooManager = std::make_shared<ToggleableProxy<IFoo>>(
    std::make_shared<FooRed>(), std::make_shared<FooBlue>()
);
// 子对象直接接收fooManager,和原来的用法一致
SubObject(fooManager);

这种方式既保留了原有代码的兼容性,又把代理和管理逻辑整合到一个模板中,职责比多重继承更清晰。

3. 设计模式判断与运行时选择的合理性

你的初始方案本质上是**代理模式(Proxy Pattern)的变体——FooManager作为IFoo的代理,把请求转发给内部的实际实例;同时它还承担了状态管理(切换实例)的职责,也可以看作是策略模式(Strategy Pattern)**的扩展:策略模式负责封装不同算法,而你额外加了一个管理器来动态切换策略实例。

这种运行时切换的方式是完全合理的,尤其是在需要根据用户操作、配置或运行环境动态改变行为的场景下。只要保证切换逻辑线程安全(如果是多线程环境)、实例持有逻辑清晰,就不会有问题。


内容的提问来源于stack exchange,提问作者Root of All Things

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 18:25:23