我应该如何设计一组相关的分类,其中只有一些分类支持特定的操作
How should I design a set of related classed where only some of them support a certain operation?
我正在用C++开发一个基于幻灯片的应用程序。每个幻灯片都有一个幻灯片项目集合,可以包括标题、按钮、矩形等项目。
只有其中一些项目支持填充,而其他项目则支持不要。
在这种情况下,实现幻灯片项目填充的最佳方式是什么?以下是我想到的两种方式:
-
创建接口
Fillable
并为幻灯片项目实现此接口支持填充,将所有与填充相关的属性保留在接口中。迭代幻灯片项目列表时,dynamic_cast进入Fillable
,如果成功,则执行与填充相关的操作。 -
制作一个
fill
类。将fill
指针作为幻灯片项目类的一部分,分配对于那些支持填充的对象,fill
对象指向fill
指针,而对于其余对象,则将其保持为空。给出一个函数GetFill
,如果它存在,它将返回项目的fill
,否则返回NULL
。
最好的方法是什么?我对性能和可维护性感兴趣。
我会将两者结合起来。制作Fillable
接口,并将其作为GetFill
方法的返回类型。这比动态铸造方法要好。使用动态转换查询接口需要实际的幻灯片项对象实现接口(如果要支持它的话(。但是,使用像GetFill
这样的访问器方法,您可以选择提供指向实现接口的其他对象的引用/指针。如果接口实际上是由这个对象实现的,那么您也可以只返回this
。这种灵活性可以帮助避免类膨胀,并促进创建可由多个类共享的可重用组件对象。
编辑:这种方法也适用于null对象模式。您可以返回一个实现接口的简单无操作对象,而不是为不支持Fillable
的对象返回一个空指针。这样就不必担心总是检查客户端代码中的空指针。
答案是这取决于。
如果不是每个对象都应该处理填充,那么我不认为必须用fill/get_fillable_instance/...
扰乱基本界面有什么意义。然而,你只需要就可以逃脱惩罚
struct slide_object
{
virtual void fill() {} // default is to do nothing
};
但这取决于您是否认为CCD_ 16应该出现在幻灯片对象抽象类中。然而,它很少应该这样做,除非不可填充是例外。
如果您需要提供两个不同的对象类(并且不超过两个(,其中一些是可填充的,另一个与可填充性无关,那么动态强制转换是正确的。在这种情况下,有两个子层次结构并在需要的地方使用动态强制转换是有意义的。
在某些情况下,我已经成功地使用了这种方法,并且它简单且可维护,前提是调度逻辑不分散(即只有一两个地方可以进行动态转换(。
如果你期望有更多类似填充的行为,那么dynamic_cast
是一个错误的选择,因为它会导致
if (auto* p = dynamic_cast<fillable*>(x))
...
else if (auto* p = dynamic_cast<quxable*>(x))
...
这很糟糕。如果您需要这个,那么实现一个Visitor模式。
创建基类SlideItem:
class SlideItem {
public:
virtual ~SlideItem();
virtual void fill() = 0;
};
然后为那些你不能填充的东西做一个空的实现:
class Button : public SlideItem {
public:
void fill() { }
};
以及其他人的适当填充实施:
class Rectangle : public SlideItem {
public:
void fill() { /* ... fill stuff ... */ }
};
把它们都放在一个容器里。。如果你想填满他们,就给每个人打电话。。。易于维护。。谁关心性能:(
如果您真的需要快速代码,那么您的第一个解决方案当然很好。但如果你这样做,确保你不必每次都想填充它。将它们投射一次,然后将指针放入可填充的容器中。如果必须填充,则对这个可填充容器进行迭代。
再说一遍,IMHO你在这方面付出了太多的努力,却没有获得合理的性能提升。(当然,我不知道你的应用程序,它可能是合理的……但通常不是(
你想要的似乎接近能力模式。你的#2接近这个模式。我会这么做:
制作填充类。将填充指针作为幻灯片项类的一部分,将填充对象分配给仅支持填充的对象的填充指针,其余对象保持为空。创建一个函数GetCapability(Capability.Fill(,如果存在,它将返回项的填充,否则返回NULL。如果某些对象已经实现了可填充接口,则可以将对象强制转换返回到可填充指针。
考虑存储变量项,如boost::variant
。
你可以定义一个boost::variant<Fillable*,Item*>
(如果你有所有权,你应该使用智能指针(,然后有一个要迭代的变体列表。
我建议使用一个用于形状的接口,并使用一个返回填充符的方法。例如:
class IFiller {
public:
virtual void Fill() = 0;
protected:
IFiller() {}
virtual ~IFiller() {}
};
class IShape {
public:
virtual IFiller* GetFiller() = 0;
protected:
IShape() {}
virtual ~IShape() {}
};
class NullFiller : public IFiller {
public:
void Fill() { /* Do nothing */ }
};
class Text : public IShape {
public:
IFiller* GetFiller() { return new NullFiller(); }
};
class Rectangle;
class RectangleFiller : public IFiller {
public:
RectangleFiller(Rectangle* rectangle) { _rectangle = rectangle; }
~RectangleFiller() {}
void Fill() { /* Fill rectangle space */ }
private:
Rectangle* _rectangle;
};
class Rectangle : IShape {
public:
IFiller* GetFiller() { return new RectangleFiller(this); }
};
我发现这种方法更容易维护和扩展,同时不会引入主要的性能问题。
- 为什么在popback()操作之后,它仍然打印完整的矢量
- 重载操作程序时出错>>用于类中的字符串 memebr
- 对字符串进行位操作
- 我可以在 C++ 中的函数体之外进行操作吗?
- MPI突然停止了对多个核心的操作
- 如何在信号处理程序和普通函数中对全局变量进行互斥读写操作
- 对字符数组中的元素执行逐位操作
- 如何在directx/c++中进行平移/缩放操作
- 逐位操作的隐式类型转换
- 为什么一个向量上的多线程操作很慢
- 排序时无法执行交换操作.我做的时候它会崩溃.为什么
- 位移操作和位掩码未检测到重复字符
- 如何进行特定的位操作?
- 当我们进行一些操作时,应该使用什么'std::string'或'std::stringstream'?
- 字符串操作 - 字符计数
- 此代码中的操作流程是什么?C/C++.
- 为我的 c++ 类介绍制作一个三角形分类器.我有几个问题
- 复制和交换习惯用法与移动操作之间的交互
- 像union_这样的 Boost.Geometry 操作如何处理浮点类型的基本不精确性?
- 我应该如何设计一组相关的分类,其中只有一些分类支持特定的操作