C++ C++;-在不引入紧耦合的情况下识别多态类族

C++ C++;-在不引入紧耦合的情况下识别多态类族,c++,polymorphism,decoupling,C++,Polymorphism,Decoupling,假设我有一个名为Component的抽象基类,它是GUI组件层次结构的根。在这种情况下,我们可能有两个子类Button和Label,它们都是抽象类,并且作为各自的具体类层次结构的根而存在 从Button继承的具体类可能包括RoundButton和SquareButton 从Label继承的具体类可能包括TextLabel和PictureLabel 最后,假设有一个聚合容器类,它保存组件对象的集合 问题是我有指向组件对象的指针,但我需要将它们标识为按钮或标签。例如,如果我想指定所有按钮的内部文本都

假设我有一个名为Component的抽象基类,它是GUI组件层次结构的根。在这种情况下,我们可能有两个子类Button和Label,它们都是抽象类,并且作为各自的具体类层次结构的根而存在

从Button继承的具体类可能包括RoundButton和SquareButton

从Label继承的具体类可能包括TextLabel和PictureLabel

最后,假设有一个聚合容器类,它保存组件对象的集合

问题是我有指向组件对象的指针,但我需要将它们标识为按钮或标签。例如,如果我想指定所有按钮的内部文本都应该使用更大的字体,我可以迭代容器中的所有组件对象,以某种方式确定哪些是按钮,并调用特定于按钮的某些方法

这些构件“族”标识自己的一种方法是使用字符串

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.h中定义一个枚举数

enum COMPONENT_FAMILY {
    COMPONENT_BUTTON = 0,
    COMPONENT_LABEL,
    // etc...
};
在本例中,getFamilyID()可以返回一个组件\u FAMILY enum,我们基本上可以比较int。不幸的是,这意味着任何新的组件族都必须在此枚举中“注册”,这很容易,但对于其他程序员来说并不完全直观。此外,该方法仍然必须是虚拟的,除非我们制作一个非静态组件,我们知道它的基数非常低(不理想)

解决这个问题的好办法是什么?在我的例子中,性能是关键,虽然类似于enum解决方案的东西似乎很简单,但我仍在想,我是否忽略了一种更好的方法

---编辑---
我意识到我应该指出,在实际系统中,容器等效物只能存储每个族中的一个组件。因此,组件实际上存储在地图中,例如:

std:map<COMPONENT_FAMILY, Component*>
std:map
应用于我这里的简单示例,这意味着一个容器只能包含1个按钮、1个标签等

这使得检查特定类型组件(日志时间)的存在变得非常容易。因此,这个问题更多的是关于如何表示元件族,以及在将元件添加到地图时如何确定元件的类型

换句话说,组件的唯一目的是将其标识为添加到容器中的特定功能,并且所有组件一起定义容器的特定行为


所以,我不需要知道组件的类型,这已经暗示了。我特别要求容器提供特定类型的组件。我需要的是组件通信其类型的一种方法,以便可以首先映射它。

动态强制转换将在不引入任何神奇常数的情况下完成此操作:

if (Button * button = dynamic_cast<Button *>(component)) {
    // It's a button.
}

您可以使用
dynamic\u cast
测试给定的
组件是否是
按钮的实例或其子类之一:

Button* btn = dynamic_cast<Button*>(component);
if (btn) {
    // it's a button!
    btn->setFontSize(150);
}
按钮*btn=动态铸件(组件);
如果(btn){
//这是一个按钮!
btn->setFontSize(150);
}
我需要将它们标识为按钮或标签

那是你的问题。这种假设虽然很常见,但通常是错误的

  • 为什么你认为你需要识别他们
  • 在代码的哪个阶段,这些知识是重要的
通过在构建时为控件分配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的共享指针,并将其构造函数注入,而不是将它们硬设为重或轻


此外,要在此设计中在对象之间共享字体,可以应用。

使用双分派器模式


只需使用
动态\u cast
,这就是它的用途。哦,这种需要通常被认为是不好的。

我认为可以适用于你的情况。通过使用此模式,您可以避免向层次结构中添加getFamilyID()类方法,并让编译器为您执行分派。这样,您就不必在代码中放入大量的
if(dynamic_cast)
类条件逻辑。

您说“不幸的是,这意味着任何新的组件族都必须在这个枚举中“注册”,这很容易,但对其他程序员来说并不完全直观”。。。为什么?您可以在API中发布枚举,还可以使用分隔符enum member
COMPONENT\u END
,它允许客户端决定是否超出枚举界限。只要通过仅在分隔符值之前直接添加新成员来保持向后兼容性,更改对客户端应该是透明的。枚举还有用于调试的符号名,可以在头文件中定义
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_;
    }
}