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

时间:2011-06-07 13:18:36

标签: c++ polymorphism decoupling

假设我有一个名为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传达它的类型,这样就可以在第一时间映射它。

6 个答案:

答案 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模式

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

答案 3 :(得分:1)

只需使用dynamic_cast,这就是它的用途。哦,对此的需求通常被认为是坏事。

答案 4 :(得分:0)

您可以使用dynamic_cast来测试给定的ComponentButton的实例还是其子类之一:

Button* btn = dynamic_cast<Button*>(component);
if (btn) {
    // it's a button!
    btn->setFontSize(150);
}

答案 5 :(得分:0)

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