假设我有一个名为Component的抽象基类,它是GUI组件层次结构的根。在这种情况下,我们可能有两个子类,Button和Label,它们都是抽象类,并作为各自具体类层次结构的根存在。
从Button继承的具体类可能包括RoundButton和SquareButton。
从Label继承的具体类可能包括TextLabel和PictureLabel。
最后,假设有一个聚合Container类,它包含一组Component对象。
问题是我有指向Component对象的指针,但我需要将它们标识为Buttons或Labels。例如,如果我想指定所有按钮的内部文本应该有更大的字体,我可以遍历Container中的所有Component对象,并以某种方式确定哪些是按钮,并调用一些特定于按钮的方法。 / p>
这些组件“系列”标识自己的一种方法是使用字符串。
class Component {
public:
virtual char const * const getFamilyID() const = 0;
};
// In Button.h
char const * const COMPONENT_BUTTON = "button";
class Button : public Component {
public:
virtual char const * const getFamilyID() const { return COMPONENT_BUTTON; };
};
// Code sample
if (strcmp(component->getFamilyID(),COMPONENT_BUTTON) == 0)
// It's a button!
这是松散耦合的,因为该组件的任务是将这些家庭定义为其子女;它不需要了解哪些家庭存在。客户端需要知道不同的组件系列,但如果它试图针对某些操作来定位特定的组件,则无法避免。
但是,假设我们有非常高的性能要求,我们希望避免比较字符串。避免将此功能设置为虚拟也很好,以便我们可以内联它。此外,如果Component的每个子类都需要声明一个全局常量,那么以某种方式修改Component类以使其成为需求或使其不必要可能会很好。
此问题的一个解决方案是在Component.h中定义一个枚举器
enum COMPONENT_FAMILY {
COMPONENT_BUTTON = 0,
COMPONENT_LABEL,
// etc...
};
在这种情况下,getFamilyID()只能返回COMPONENT_FAMILY枚举,我们基本上只需比较一下。不幸的是,这意味着任何新的组件系列都必须在此枚举中“注册”,这很容易但对其他程序员来说并不完全直观。此外,该方法仍然必须是虚拟的,除非我们创建一个非静态COMPONENT_FAMILY成员,我们知道该成员的基数极低(不理想)。
解决这个问题的好方法是什么?在我的情况下,性能是关键,虽然类似于枚举解决方案似乎很容易,但我想知道我是否忽视了更好的方法。
---编辑---
我意识到我应该指出,在实际系统中,Container等价物只能存储每个族的1个Component。因此,组件实际上存储在地图中,例如:
std:map<COMPONENT_FAMILY, Component*>
应用于我的简单示例,这意味着Container只能包含1个Button,1个Label等。
这使得检查特定类型的组件(日志时间)的存在变得非常容易。因此,这个问题更多的是关于如何表示COMPONENT_FAMILY,以及当我将它添加到地图时如何确定Component的类型。
换句话说,组件的唯一目的是将其标识为添加到Container的特定功能,并且所有组件一起定义容器的特定行为。
所以,我不需要知道组件的类型,这已经暗示了。我特别要求Container提供特定类型的Component。我需要的是一种方式让Component传达它的类型,这样就可以在第一时间映射它。
答案 0 :(得分:5)
我需要将它们标识为按钮或标签。
那是你的问题。这种假设虽然很常见,但通常是错误的。
通过在施工时为控件分配UI策略,您可以绕过“要求”来了解其具体类型。在基类的绘制时间(或每当)检索该UI策略的字体属性:
class IVisualStrategy
{
...
virtual const Font& GetFont() const = 0;
...
};
class HeavyVisuals : public IVisualStrategy
{
Font font_;
...
HeavyVisuals() : font_(15, Bold) {}
virtual const Font& GetFont() const { return font_; }
...
};
class LightVisuals : public IVisualStrategy
{
Font font_;
...
LightVisuals() : font_(12, Regular) {}
virtual const Font& GetFont() const { return font_; }
...
};
从基地检索:
class Control
{
...
private:
void OnPaintOrSomething()
{
DrawTextWithFont(GetVisualStrategy().GetFont());
}
virtual const IVisualStrategy& GetVisualStrategy() const = 0;
};
class Button : public Control
{
HeavyVisualStrategy visualStrategy_;
...
private:
virtual const IVisualStrategy& GetVisualStrategy() const
{
return visualStrategy_;
}
}
class Label : public Control
{
LightVisualStrategy visualStrategy_;
...
private:
virtual const IVisualStrategy& GetVisualStrategy() const
{
return visualStrategy_;
}
}
更灵活的设计是在具体的控件类和构造函数中保持对IVisualStrategy的共享指针 - 注入它,而不是将它们硬设置为Heavy或Light。
此外,要在此设计中的对象之间共享字体,可以应用Flyweight pattern。
答案 1 :(得分:4)
动态强制转换会在不引入任何魔术常量的情况下执行此操作:
if (Button * button = dynamic_cast<Button *>(component)) {
// It's a button.
}
更新:现在您已经根据基于类型的地图键的要求更新了问题,动态投射将无效。避免中央枚举耦合的一种方法可能是使用静态密钥生成器,如:
class Component
{
public:
virtual int getFamilyID() const = 0;
static int generateFamilyID()
{
static int generator = 0;
return generator++;
}
};
class Label
{
public:
virtual int getFamilyID() const {return familyID;}
private:
static const int familyID;
};
class Button
{
public:
virtual int getFamilyID() const {return familyID;}
private:
static const int familyID;
};
const int Label::familyID = Component::generateFamilyID();
const int Button::familyID = Component::generateFamilyID();
答案 2 :(得分:1)
使用Double Dispatcher模式
答案 3 :(得分:1)
只需使用dynamic_cast
,这就是它的用途。哦,对此的需求通常被认为是坏事。
答案 4 :(得分:0)
您可以使用dynamic_cast
来测试给定的Component
是Button
的实例还是其子类之一:
Button* btn = dynamic_cast<Button*>(component);
if (btn) {
// it's a button!
btn->setFontSize(150);
}
答案 5 :(得分:0)
我认为visitor pattern可以应用于您的案例。通过使用此模式,您可以避免在层次结构中添加 getFamilyID()类型的方法,并让编译器为您进行调度。这样,您就不必在代码中添加大量if (dynamic_cast<> )
条件逻辑。