1

画面に描画できるアイテムを表す Item クラスがありますアイテムがテキスト、画像、または無地の四角形であるとしましょう。
そのようなアイテムのコレクションを含むクラスもあります。

これらのクラスを実装する 2 つの異なるアプローチを想像できます。次のようなインターフェイスでこれらのアイテムを描画する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& 参照を具体的な型にキャストする必要があることです。 Draw関数で。

2 番目のアプローチ:

class Item
{
    ItemType Type;

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

enum class ItemType { Text, Image, Rect };

class ItemCollection
{
    vector<Item> Items;
}

このソリューションでは、1 つのクラスにさまざまなアイテムのすべてのデータ メンバーが含まれています。利点は、コレクション クラスのベクトルに実際の Itemを含めることができるようになり、Draw 関数でキャストが不要になることです。
ただし、欠点は、Item クラスのメモリ使用量が最適でないことです。これは、すべての種類のアイテム インスタンスに、実際には必要のない多数のフィールドが含まれるためです。また、後で追加の項目タイプが追加された場合、Item クラスはすべての新しいフィールドで雑然とします。

(第 3 の可能なアプローチは、抽象仮想 Draw メソッドを基本 Item クラスに配置することであることは知っていますが、私の状況では、これらのクラスにはデータのみを含めることができ、ロジックを含めることができないため、それを行うことはできません。)

このような状況では、通常、どのソリューションが優先されますか?

4

2 に答える 2

2

訪問者パターン

3 番目に考えられるアプローチは、抽象仮想 Draw メソッドを基本 Item クラスに配置することであることはわかっていますが、私の状況では、これらのクラスにはデータのみを含めることができ、ロジックを含めることができないため、それを行うことはできません。

ビジター パターンを試してください。すべてのロジックは、オブジェクトではなくビジター クラスになります。各オブジェクトには、accept(Visitor&) という仮想メソッドが 1 つだけあります。

ライブデモ

#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

(第 3 の可能なアプローチは、抽象仮想 Draw メソッドを基本 Item クラスに配置することであることは知っていますが、私の状況では、これらのクラスにはデータのみを含めることができ、ロジックを含めることができないため、これを行うことはできません。)

質問のタイトルにあるように、ポリモーフィズムを探している場合、これは正しいアプローチです。受け入れられない場合は、プログラムの修正を検討してください。
あなたが言及した他の2つのアプローチは、どちらも変数を持つことに依存していItemTypeます。これは、まさにポリモーフィズムが回避することを意図しています。

また、後で追加の項目タイプが追加された場合、Item クラスはすべての新しいフィールドで雑然とします。

Drawer の実装は新しいオブジェクト タイプについて知る必要がないため、これもポリモーフィズムによって解決されます。

シンプルに保ちたい場合は、最初の例で問題ありませんが、type扱っているオブジェクトの種類がわかるように変数が必要です。

于 2013-04-08T13:34:11.913 に答える