我有一个 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方法,但我不能这样做,因为在我的情况下这些类只能包含数据,而且没有逻辑。)
在这种情况下,这通常是首选解决方案吗?
答案 0 :(得分:2)
我知道第三种可能的方法是在基础Item类中放置一个抽象的虚拟Draw方法,但我不能这样做,因为在我的情况下这些类只能包含数据,而且没有逻辑。
尝试Visitor Pattern - 您的所有逻辑都将在访客类中而不是对象中。每个对象只有一个虚拟方法 - 接受(访客&amp;)。
#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
但是,缺点是Item类的内存使用量不是最佳的,因为每种类型的项目实例都包含许多它并不真正需要的字段。此外,如果以后添加其他项目类型,则Item类将会混乱其所有新字段。
将Boost.Variant视为该技术的优化。您的变体的大小为最大值,而不是所有字段的累积:
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
答案 1 :(得分:1)
(我知道第三种可能的方法是在基础Item类中放置一个抽象的虚拟Draw方法,但我不能这样做,因为在我的情况下这些类只能包含数据,而且没有逻辑。)
如果您正在寻找多态性,这是正确的方法,正如问题标题所述。如果您不能接受,请考虑修改您的计划
您提到的另外两种方法都依赖于ItemType
变量,这正是多态性要避免的变量。
此外,如果添加了以后的其他项目类型,那么Item类将会混乱其所有新字段。
这也可以通过多态来解决,因为Drawer实现不需要知道新的对象类型。
如果你想保持简单,第一个例子没问题,但是你需要一个type
变量,这样你就可以知道你正在处理哪种对象。