1

只是为了好玩,我正在为 Windows 开发一个 XUL 实现。在 XUL 中,UI 元素可以像这样用 XML 编写:

<window width="800" height="600"></window>

我正在考虑一个获取和设置元素属性的系统。它工作得很好,但我不确定在这里使用钻石继承是否有潜在危险。我在下面发布了一个完整的代码示例:

#include <boost/lexical_cast.hpp>
#include <string>
#include <map>


class Attribute
{
public:
    virtual void get(std::string & outValue) = 0;
    virtual void set(const std::string & inValue) = 0;

    static int String2Int(const std::string & inString)
    {
        return boost::lexical_cast<int>(inString);
    }

    static std::string Int2String(int inValue)
    {
        return boost::lexical_cast<std::string>(inValue);
    }
};


class Width : public Attribute
{
public:
    Width(){}

    virtual void get(std::string & outValue)
    {
        outValue = Int2String(getWidth());
    }

    virtual void set(const std::string & inValue)
    {
        setWidth(String2Int(inValue));
    }

    virtual int getWidth() const = 0;

    virtual void setWidth(int inWidth) = 0;
};


class Height : public Attribute
{
public:
    Height(){}

    virtual void get(std::string & outValue)
    {
        outValue = Int2String(getHeight());
    }

    virtual void set(const std::string & inValue)
    {
        setHeight(String2Int(inValue));
    }

    virtual int getHeight() const = 0;

    virtual void setHeight(int inHeight) = 0;
};

class Element : public Width,  // concerning the is-a vs has-a philosophy
                public Height  //   => see my note below
{
public:
    Element() :
        mWidth(0),
        mHeight(0)
    {
        // STATIC CAST NEEDED HERE OTHERWISE WE GET COMPILER ERROR:
        // error C2594: '=' : ambiguous conversions from 'Element *const ' to 'Attribute *'
        mAttrControllers["width"] = static_cast<Width*>(this);
        mAttrControllers["height"] = static_cast<Height*>(this);
    }

    void setAttribute(const std::string & inAttrName, const std::string & inAttrValue)
    {
        Attributes::iterator it = mAttrControllers.find(inAttrName);
        if (it != mAttrControllers.end())
        {
            Attribute * attribute = it->second;
            attribute->set(inAttrValue);
        }
    }

    std::string getAttribute(const std::string & inAttrName)
    {
        std::string result;
        Attributes::iterator it = mAttrControllers.find(inAttrName);
        if (it != mAttrControllers.end())
        {
            Attribute * attribute = it->second;
            attribute->get(result);
        }
        return result;
    }

    virtual int getWidth() const
    {
        return mWidth;
    }

    virtual void setWidth(int inWidth)
    {
        mWidth = inWidth;
    }

    virtual int getHeight() const
    {
        return mHeight;
    }

    virtual void setHeight(int inHeight)
    {
        mHeight = inHeight;
    }

private:
    typedef std::map<std::string, Attribute *> Attributes;
    Attributes mAttrControllers;
    int mWidth;
    int mHeight;
};


int main()
{
    Element el;
    el.setAttribute("width", "800");
    el.setAttribute("height", "600");
    int w = el.getWidth();
    int h = el.getHeight();
    return 0;
}

我认为没关系,因为基类 Attributes 没有数据成员,所以那里不会出现冲突。但我想我会与社区核实。非常感谢您的见解!

编辑 关于“is-a”与“has-a”,以及“偏好组合优于继承”的评论我有这样的说法:

  • 这里继承有一个优势。如果 Element 继承了 Width ,则强制实现 getWidth 和 setWidth 方法。所以添加一个属性意味着元素界面的“自动”更新。
  • 我最初将这些类命名为 AttributeController、WidthController 和 HeightController,但我发现它们太冗长了。你可以说我的 Element 是一个属性控制器。(好吧,这很蹩脚,但并非不真实!)
  • 进一步证明:Width 和 Height 的定义不包含任何数据成员。Element 类实际上它们。Width 和 Height 类只提供接口。所以这更像是一种可以做的关系。
4

4 回答 4

6

在您的场景中,Element 可能不应该从 Width 和 Height 继承,而是 Width 和 Height 应该是元素的数据成员。它是与 is-a 相对的组合,因为可以说 Element is-not-a Width 或 Height 但由 Width 和 Height 组成(可能还有其他一些东西)。

于 2009-09-27T16:40:53.483 回答
2

当您需要将Element对象用作Width对象时, Element才应继承Width继承不是为了代码重用

也许值得你看看 boost::program_options 库。我喜欢他们注册属性的奇特方式。

于 2009-09-27T21:16:20.027 回答
0

如果我要这样做,我会在 Attribute 上做一个虚拟继承。我认为这不会很重要,但如果它最终很重要,那将最大限度地减少重复。

于 2009-09-27T16:41:24.980 回答
-1

首先,如果您面临菱形问题,请使用虚拟继承public virtual,即使用而不是仅public在基类上使用。

其次,它没有很好地定义你的get- 和set- 将做什么,因为有两种实现。我唯一一次使用多重继承是在我扩展纯虚拟类(又名接口)时。Java 不支持多重继承是一个很好的理由。

第三,更重要的是,这似乎是对面向对象的继承(“is a”)与聚合(“has a”)的误解之间的经典案例。在确定一个类是否应该继承另一个类时,您可以使用两个非常简单的准则。如果您有 A 类和 B 类继承 A 类,那么“A 是 B”这句话应该是有意义的。如果“A 有 B”听起来更好,那么您真的应该考虑让 B 成为 A 的成员。

在您的情况下,“元素是高度”和“元素是宽度”这句话真的没有意义。“元素有高度”非常有意义。

于 2009-09-27T16:51:05.930 回答