如何避免vector<unique_ptr<Element>>元素的dynamic_cast,实现关注点分离的处理器调用?
问题描述
我正在改进自己之前设计的一个C++方案,目前的设计存在缺陷。核心需求如下:
- 存在一个
Element基类,被多个子类继承; - 存在一个
DrawHandler基类,被多个子类继承; - 存在一个
Surface类,维护Element列表,并通过调用对应DrawHandler渲染这些元素。
这个设计需要支持通过新增Element子类和DrawHandler子类扩展项目,无需修改现有代码。此外,后续还可能加入不同类型的处理器,比如CommandHandler等。
当前临时方案
Surface类维护一个std::vector<std::unique_ptr<Element>> elements成员,其renderFrame()方法实现如下:
for(const auto& element: elements){ auto handler = Tester::getHandler(*element); handler->draw(*this, *element); }
我对Tester::getHandler的实现非常不满意,它的逻辑大致如下:
static std::unique_ptr<DrawHandler> getHandler(const Element& element){ if (auto* c = dynamic_cast<const Subclass1*>(&element)) { return std::make_unique<SubClass1DrawHandler>(); } if (auto* c = dynamic_cast<const Subclass2*>(&element)) { return std::make_unique<Subclass2DrawHandler>(); } // ... 更多类型判断 }
尝试过的失败方案
我试过几种方法,但因为对C++不够精通,始终没找到比上述方案更好的实现:
- 强制
Element子类实现getHandler会污染设计,违反SOLID原则。而且如果处理器类型增多(比如DrawHandler、ControlHandler等),每个Element子类都要新增对应的getHandler方法,扩展性极差。 - 第一次尝试模板失败:
std::vector<std::unique_ptr<Element>>会擦除类型信息,导致模板无法获取具体的子类类型:
template <typename ElementType> void drawElement(const ElementType& element){ // 这里ElementType == Element,子类特化信息被擦除 } void someFunc(){ // elements是vector<unique_ptr<Element>>,已存入Subclass1等子类实例 for(const auto& element: elements){ drawElement(element); } }
- 第二次尝试模板时,我把
elements改成vector<pair<unique_ptr<Element>, unique_ptr<DrawHandler>>>,并修改Surface::addElement方法:
template <typename T> void addElement(T&& element) { using DecayedType = std::decay_t<T>; auto elPtr= std::make_unique<DecayedType>(std::forward<T>(element)); auto handlerPtr = std::make_unique<typename DrawHandlerTraits<DecayedType>::HandlerType>(); elements.emplace_back(std::move(elPtr), std::move(handlerPtr)); }
对应的DrawHandlerTraits实现大致如下:
template <typename ElementType> struct DrawHandlerTraits; template <> struct DrawHandlerTraits<Subtype1>{ using HandlerType = Subtype1DrawHandler; }; template <> struct DrawHandlerTraits<Subtype2>{ using HandlerType = Subtype2DrawHandler; }; // ... 更多特化
但这种方案只是把dynamic_cast的条件判断换成了模板特化,本质上还是要针对每个新增类型修改代码。而且如果新增处理器类型,还得修改elements的结构,比如有四种处理器时,elements会变成:
vector<tuple<unique_ptr<Element>, unique_ptr<DrawHandler>, unique_ptr<CommandHandler>, unique_ptr<AudioHandler>, unique_ptr<TextDescriptionHandler>>> elements;
需求
我希望找到一种设计模式,能支持如下用法:
Subtype1 x; Handler[Draw[Subtype1]] handler1 ; // 实际类型为Subtype1DrawHandler Subtype2 y; Handler[Command[Subtype2]] handler2 ; // 实际类型为Subtype2CommandHandler Handler[Draw[Subtype2]] handler3; // 实际类型为Subtype2DrawHandler
这种用法可以用模板实现,但存在一个核心问题:
// ... surface.addElement(x); surface.addElement(y); surface.render(); // ... Surface::render(){ // elements是vector<unique_ptr<Element>>,此时Subtype1和Subtype2的类型信息已被隐藏 // 但模板需要这些具体类型信息才能工作 }
内容的提问来源于stack exchange,提问作者AVALFINN
相关产品推荐
相关产品推荐

