1

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

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

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& 引用转换为其具体类型在绘图函数中。

第二种方法:

class Item
{
    ItemType Type;

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

enum class ItemType { Text, Image, Rect };

class ItemCollection
{
    vector<Item> Items;
}

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

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

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

4

2 回答 2

2

访客模式

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

尝试访问者模式——你所有的逻辑都在访问者类而不是对象中。每个对象只有一个虚拟方法——accept(Visitor&)

现场演示

#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视为该技术的优化。您的变体将具有max item的大小,而不是所有字段的累积:

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

现场演示

#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
于 2013-04-08T13:39:38.917 回答
1

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

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

此外,如果稍后添加了其他项目类型,则 Item 类将与所有新字段混在一起。

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

如果您打算保持简单,第一个示例是可以的,但是您需要一个type变量,以便您可以判断您正在处理哪种对象。

于 2013-04-08T13:34:11.913 回答