普通数据对象的多态性的正确方法是什么?

时间:2013-04-08 13:28:38

标签: c++ coding-style polymorphism

我有一个 Item 类,表示可以在屏幕上绘制的项目。假设该项目可以是文本,图像或纯色矩形 我还有一个包含这些项目集合的类。

我可以想象两种不同的方法来实现这些类。我有一个抽屉类来使用如下界面绘制这些项目:

Draw(Item& item);

第一种方法:

class Item
{
    Point Position;
}

class Text : public Item
{
    string Text;
}

class Image : public Item
{
    string FilePath;
}

class Rect : public Item
{
    Color FillColor;
}

class ItemCollection
{
    vector<Item*> Items;
}

第一种方法使用继承来区分不同类型的项目。这个解决方案的缺点是,当将项目存储在向量中以创建异构集合时,我必须使用某种句柄(上例中的一个简单指针),以及Item&amp;引用必须在Draw函数中转换到其具体类型。

第二种方法:

class Item
{
    ItemType Type;

    Point Position;
    string Text;
    string FilePath;
    Color FillColor;
};

enum class ItemType { Text, Image, Rect };

class ItemCollection
{
    vector<Item> Items;
}

在此解决方案中,单个班级包含不同项目的所有数据成员。好处是,现在集合类中的向量可以包含实际项,并且在Draw函数中将需要无投射
但是,缺点是Item类的内存使用不是最佳的,因为每种类型的项实例都包含许多它并不真正需要的字段。此外,如果以后添加其他项目类型,则Item类将会混乱其所有新字段。

(我知道第三种可能的方法是在基础Item类中放置一个抽象的虚拟Draw方法,但我不能这样做,因为在我的情况下这些类只能包含数据,而且没有逻辑。)

在这种情况下,这通常是首选解决方案吗?

2 个答案:

答案 0 :(得分:2)

访客模式

  

我知道第三种可能的方法是在基础Item类中放置一个抽象的虚拟Draw方法,但我不能这样做,因为在我的情况下这些类只能包含数据,而且没有逻辑。

尝试Visitor Pattern - 您的所有逻辑都将在访客类中而不是对象中。每个对象只有一个虚拟方法 - 接受(访客&amp;)

LIVE DEMO

#include <iostream>
#include <ostream>
#include <utility>
#include <memory>
#include <vector>
#include <string>

using namespace std;

using Color = int;

struct Text;
struct Image;
struct Rect;

struct Visitor
{
    virtual void operator()(const Text &x) const=0;
    virtual void operator()(const Image &x) const=0;
    virtual void operator()(const Rect &x) const=0;
};

struct IVisitable
{
    virtual void accept(Visitor&)=0;
    virtual ~IVisitable(){}
};
template<typename Derived>
struct Visitable : IVisitable
{
    void accept(Visitor &v) override
    {
        v(*static_cast<Derived*>(this));
    }
};

struct Text: Visitable<Text>
{
    string Text = "text";
};

struct Image: Visitable<Image>
{
    string FilePath = "path";
};

struct Rect: Visitable<Rect>
{
    Color FillColor = 11;
};

struct Draw: Visitor
{
    void operator()(const Text &x) const override
    {
        cout << "Text: " << x.Text << endl;
    }
    void operator()(const Image &x) const override
    {
        cout << "image: " << x.FilePath << endl;
    }
    void operator()(const Rect &x) const override
    {
        cout << "Rect: " << x.FillColor << endl;
    }

};

int main()
{
    vector<unique_ptr<IVisitable>> items;
    items.emplace_back(new Text);
    items.emplace_back(new Image);
    items.emplace_back(new Rect);
    Draw v;
    for(auto &&x: items)
        x->accept(v);
}

输出是:

Text: text
image: path
Rect: 11

Boost.Variant

  

但是,缺点是Item类的内存使用量不是最佳的,因为每种类型的项目实例都包含许多它并不真正需要的字段。此外,如果以后添加其他项目类型,则Item类将会混乱其所有新字段。

Boost.Variant视为该技术的优化。您的变体的大小为最大值,而不是所有字段的累积:

std::vector<boost::variant<Text, Image, Rect>> items;

LIVE DEMO

#include <boost/range/algorithm.hpp>
#include <boost/variant.hpp>
#include <iostream>
#include <ostream>
#include <utility>
#include <vector>
#include <string>

using namespace boost;
using namespace std;

using Color = int;

struct Text
{
    string Text;
};

struct Image
{
    string FilePath;
};

struct Rect
{
    Color FillColor;
};

struct Draw : static_visitor<void>
{
    void operator()(const Text &x) const
    {
        cout << "Text: " << x.Text << endl;
    }
    void operator()(const Image &x) const
    {
        cout << "image: " << x.FilePath << endl;
    }
    void operator()(const Rect &x) const
    {
        cout << "Rect: " << x.FillColor << endl;
    }

};

int main()
{
    vector<variant<Text, Image, Rect>> items = 
    {
        Text{"text"},
        Image{"path"},
        Rect{55}
    };
    Draw v;
    for_each(items,apply_visitor(v));
}

输出是:

Text: text
image: path
Rect: 55

答案 1 :(得分:1)

  

(我知道第三种可能的方法是在基础Item类中放置一个抽象的虚拟Draw方法,但我不能这样做,因为在我的情况下这些类只能包含数据,而且没有逻辑。)

如果您正在寻找多态性,这是正确的方法,正如问题标题所述。如果您不能接受,请考虑修改您的计划 您提到的另外两种方法都依赖于ItemType变量,这正是多态性要避免的变量。

  

此外,如果添加了以后的其他项目类型,那么Item类将会混乱其所有新字段。

这也可以通过多态来解决,因为Drawer实现不需要知道新的对象类型。

如果你想保持简单,第一个例子没问题,但是你需要一个type变量,这样你就可以知道你正在处理哪种对象。