2

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

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

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

最后,假设有一个聚合 Container 类,其中包含 Component 对象的集合。

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

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

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 类以使其成为要求或使其成为不必要可能会很好。

解决此问题的一种方法是在 Component.h 中定义一个枚举器

enum COMPONENT_FAMILY {
    COMPONENT_BUTTON = 0,
    COMPONENT_LABEL,
    // etc...
};

在这种情况下 getFamilyID() 可以只返回一个 COMPONENT_FAMILY 枚举,我们基本上可以只比较整数。不幸的是,这意味着任何新的组件系列都必须在这个枚举中“注册”,这很容易,但对于其他程序员来说并不完全直观。此外,该方法仍然必须是虚拟的,除非我们创建一个非静态 COMPONENT_FAMILY 成员,我们知道该成员将具有极低的基数(不理想)。

解决这个问题的好方法是什么?就我而言,性能是关键,虽然类似于 enum 解决方案的东西似乎很容易,但我想知道我是否忽略了更好的方法。

--- 编辑 ---
我意识到我应该指出,在实际系统中,Container 等效项只能存储每个系列的 1 个组件。因此,组件实际上存储在一个映射中,例如:

std:map<COMPONENT_FAMILY, Component*>

应用于我这里的简单示例,这意味着容器只能包含 1 个按钮、1 个标签等。

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

换句话说,组件的唯一目的是被识别为添加到容器的特定功能,所有组件一起定义容器的特定行为。

所以,我不需要知道组件的类型,这已经隐含了。我专门向容器询问特定类型的组件。我需要的是一种让组件传达其类型的方法,以便可以首先对其进行映射。

4

6 回答 6

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。

此外,为了在此设计中的对象之间共享字体,可以应用享元模式。

于 2011-06-07T13:29:33.227 回答
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();
于 2011-06-07T13:28:09.330 回答
1

使用双调度模式

http://en.wikipedia.org/wiki/Double_dispatch

于 2011-06-07T13:31:43.697 回答
1

只需使用dynamic_cast,这就是它的用途。哦,对此的需要通常被认为是不好的。

于 2011-06-07T13:33:23.320 回答
0

您可以使用dynamic_cast来测试给定Component是否是其子类的实例Button或其子类之一:

Button* btn = dynamic_cast<Button*>(component);
if (btn) {
    // it's a button!
    btn->setFontSize(150);
}
于 2011-06-07T13:28:51.967 回答
0

我认为访问者模式可以应用于您的案例。通过使用这种模式,您可以避免将getFamilyID()类型的方法添加到您的层次结构中,并让编译器为您进行调度。这样您就不必if (dynamic_cast<> )在代码中添加很多条件逻辑。

于 2011-06-07T14:02:38.190 回答