3

次の状況を想像してみてください。

いろいろなモンスター工場を作りたいです。structこれらのモンスターファクトリーは、アレイによって提供されるデータに基づいてモンスターを作成します。モンスターはこれらの統計のみが異なるため、モンスターごとにサブクラスを作成するのはやり過ぎです。

struct monster_data
{
    int HP;
    int strength;
    int speed;
    // even more attributes
};

クラスは、 :monsterに基づいてモンスターのすべての動作を処理できます。monster_data

class monster
{
    public:
        monster(monster_data* initial_stats, int length);

    void attack();
    void walk();
    void die();
    // and so forth
};

ここまでは順調ですね。これで、ハードコードされた配列monster_factoryに基づいてモンスターを作成するクラスができました。monster_data

const monster_data district1_monsters[]
{
    { 500, 20,  4 }, // monster1
    { 550,  5, 12 }, // monster2
    { 420,  8, 10 }, // monster3
    { 310, 30,  7 }, // monster4
    // 100 more monsters
};

class monster_factory
{
    public:
        monster_factory(monster_data* monster_to_create) ;
        monster* create_random_monster();
};

私の問題はmonster_factories、リストにわずかな違いがあるいくつかの地区でいくつかをサポートする必要があることです。

const monster_data district1_monsters[]
{
    { 500, 20,  4 }, // monster1
    { 550,  5, 12 }, // monster2
    { 420,  8, 10 }, // monster3
    { 310, 30,  7 }, // monster4
    // 100 more monsters
};

const monster_data district2_monsters[]
{
    { 500, 20,  4 }, // monster1
    { 750,  5, 12 }, // MONSTER2B <<
    { 420,  8, 10 }, // monster3
    { 310, 30,  7 }, // monster4
    // monsters 5 - 80 from district 1
};

const monster_data district3_monsters[]
{
    { 500, 20,  4 }, // monster1
    { 550,  5, 12 }, // monster2
    { 720, 80, 10 }, // MONSTER3B <<
    { 310, 30,  7 }, // monster4
    // monsters 8 - 90 from district 1
};

配列データをコピーして貼り付けるのではなく、さまざまなバージョン間でデータがほぼ同じであるため、何らかの方法で配列データを継承したいと思います。配列宣言全体をコピーstructして、わずかに異なるバリアントを作成するのは間違った方法のようです。残念ながら、第2地区と第3地区はデータを追加せず、既存のエントリを変更して省略します。もちろん、彼らは複数のモンスターも変更します。

さらに、第1地区のモンスターデータの変更は、第2地区と第3地区にも適用されるはずです。

もう1つの問題は、地区1、2、および3とはまったく関係のないモンスターデータを持つ地区があることです。

const monster_data district4_monsters[]
{
    { 100, 20, 10 }, // monster 401
    { 200, 50, 20 }, // monster 402
    { 300, 40,  5 }, // monster 403
    { 400, 30, 30 }, // monster 404
    // 20 more monsters unrelated to district 1,2 & 3
};

ここで質問になります。冗長なmonster_data宣言を回避しmonster_data、既存の宣言から派生するか、まったく新しい宣言を使用する地区を追加できるように、アウトライン化された設計をどのように変更できますか?

ボーナスポイント、モンスター統計リストのすべてのバリアントに対して1つのファクトリインスタンスのみが存在できるように設計されている場合。

4

6 に答える 6

2

これは、各レイヤーの変更で「デフォルト」テーブルを装飾することにより、デコレータパターンによってエレガントに解決できます。

class MonsterTable
{
  public:
    virtual monster_data const* getMonsterForIndex(int i)=0;
};

class DefaultMonsterTable : public MonsterTable
{
  public:

    monster_data const* getMonsterForIndex(int i)
    {
      return district1_monsters+i;
    } 
};

class OverlayMonsterTable : public MonsterTable
{
public:
  //public for brevity, should be private in real code - can also be std::map
  std::unordered_map<int, monster_data> OverlayData;

  // Init this with the "previous layer", which is always the Default table in your examples
  MonsterTable* Decorated;

  monster_data const* getMonsterForIndex(int i)
  {
    typedef std::unordered_map<VGLindex, monster_data>::const_iterator Iterator;
    Iterator Overlay=OverlayData.find(i);
    if (Overlay!=OverlayData.end()) // Monster data was changed in this layer
      return &Overlay->second;

    return Decorated->getMonsterFromIndex(i); // Defer to next layer
  } 
};

次に、上位の地区のすべての「変更」をOverlayDataに追加し、OverlayMonsterTableにデフォルトのテーブル(district1)を参照させます。

データの省略をサポートするには、インデックスを再マップする別のデコレータ「レイヤー」を追加するか(たとえば、[0...80]を[0...10]、[30 ... 100]にマップ)、統合することができます。この機能を既存のOverlayMonsterTableに追加します。いずれにせよ、あなたは完全な柔軟性を持っています。例えば:

class OmitMonsterTable : public MonsterTable
{
public:
  int OmitBegin, OmitEnd;
  MonsterTable* Decorated;

  monster_data const* getMonsterForIndex(int i)
  {
    if (i > OmitBegin)
      i += OmitEnd;

    return Decorated->getMonsterForIndex(i);
  } 
};

あなたのファクトリはMonsterTableポインタ/参照を取得するだけです。

于 2012-07-18T09:56:12.513 に答える
1

バイナリにデータを格納することは、多くの場合、悪い習慣であり、特に大量のデータになる場合は拡張できません。データの単純な継承をサポートする独自のミニ言語を定義し、それをを含むクラスに解析するのにそれほど問題はありませんunordered_map。これにより、必要に応じて、単純なデータ共有とより複雑なプロパティシステムを実装することもできます。

于 2012-07-18T11:34:36.387 に答える
1

あなたは「継承」という言葉を使い続けますが、ここでは継承を絶対に考慮しません。1つのタイプの動作、つまり1つのタイプのファクトリクラスしかなく、異なるデータでファクトリを初期化するだけです。

monster_dataすべての異なる値を持つ1つの大きな配列があります。

const monster_data all_data[] = {
  // district1_monsters
  { 500, 20,  4 }, // monster1
  { 550,  5, 12 }, // monster2
  { 420,  8, 10 }, // monster3
  { 310, 30,  7 }, // monster4
  // 100 more monsters
  // ...
  // district 2 monsters (index 104)
  { 750,  5, 12 }, // MONSTER2B <<
  // district 3 monsters (index 105)
  { 720, 80, 10 }, // MONSTER3B <<
  // district4 monsters (index 106)
  { 100, 20, 10 },
  { 200, 50, 20 },
  { 300, 40,  5 },
  { 400, 30, 30 },
  // 20 more monsters unrelated to district 1,2 & 3
  // ...
};

次に、適切なシーケンスを含むシーケンスを作成します。

typedef std::vector<monster_data> data_seq;

data_seq district1_data(all_data, all_data + 104);

data_seq district2_data(all_data, all_data + 80);
district2_data[2] = all_data[104];

data_seq district3_data(all_data, all_data + 3);
district3_data.push_back( all_data[105] );
district3_data.insert(district3_data.end(), all_data+8, all_data+90);

data_seq district4_data(all_data+106, all_data + 126);

次に、これらのシーケンスからファクトリを作成します。

class monster_factory
{
public:
  monster_factory(const data_seq& monsters) ;
  monster* create_random_monster();
};

monster_factory district1_factory(district1_data);
monster_factory district2_factory(district2_data);
monster_factory district3_factory(district3_data);
monster_factory district4_factory(district4_data);

monster_dataタイプが3つの整数のみの場合は、問題ありません。それがより大きなクラスである場合は、配列の要素へのポインターのみを保持data_seqするように作成できます。これにより、オブジェクトのコピーが回避されます。オブジェクトはマスター配列に存在するだけで、他のすべてはポインターを介してそれらのマスターコピーを参照します。ベクトルオブジェクトを作成するには、要素の単純なコピーではなく、配列要素のアドレスを入力する必要があるため、少し手間がかかりますが、プログラムの起動時に1回だけ実行する必要があります。したがって、それを正しく行うためにもう少しコードを書くことは価値があります。vector<const monster_data*>all_datamonster_dataall_data

struct address_of {
  const monster_data* operator()(const monster_data& m) const
  { return &m; }
};

// ...

typedef std::vector<const monster_data*> data_seq;

data_seq district1_data;
std::transform(all_data, all_data + 104,
               std::back_inserter(district1_data), address_of());

data_seq district2_data;
std::transform(all_data, all_data + 80,
               std::back_inserter(district2_data), address_of());
district2_data[2] = &all_data[104];

data_seq district3_data;
std::transform(all_data, all_data + 3,
               std::back_inserter(district3_data), address_of());
district3_data.push_back( all_data[105] );
std::transform(all_data+8, all_data + 90,
               std::back_inserter(district3_data), address_of());

data_seq district4_data;
std::transform(all_data+106, all_data + 126,
               std::back_inserter(district4_data), address_of());

各地区のシーケンスを初期化するための代替の、おそらくより保守しやすい方法は、各地区のインデックスの配列を持つことです。

int district1_indices[] = { 0, 1, 2, 3, 4, ... 103 };
int district2_indices[] = { 0, 1, 104, 3, 4, ... 79 };
int district3_indices[] = { 0, 1, 2, 105, 7, 8, 9, 10 ... 89 };
int district4_indices[] = { 106, 107, 108, 109 ... 125 };

次に、それらの配列の1つ(およびその長さ)を使用してファクトリを構築します。ファクトリは、リストからインデックスを選択し、それを使用してインデックスを作成しall_data、を取得できますmonster_data

于 2012-07-18T09:20:40.090 に答える
0

モンスターを頼んだときに地区を通過する工場が1つあります。次に、次のようなことができます(擬似コードのみ)

getMonster(int district)
{
    monster_data dat = getRandomBaseMonster();
    // dat has to be a copy so we don't stomp in the base data
    if (district == 2) {
        dat.HP += 10;
    }
    return dat;
}
于 2012-07-18T08:33:21.523 に答える
0

1つの解決策は、「標準」モンスターデータを含むベーステーブルを用意し、各地区について、変更されたモンスターのリストのみを含むテーブルを作成することです。

このようなもの:

const monster_data base_monsters[] = {
    { 500, 20,  4 }, // monster1
    { 550,  5, 12 }, // monster2
    { 420,  8, 10 }, // monster3
    { 310, 30,  7 }, // monster4
    // 100 more monsters
};

struct monster_change_data
{
    int monster;               /* Index into base table */
    struct monster_data data;  /* Modified monster data */
};

const struct monster_change_data district2_monsters[] = {
    { 1, { 750,  5, 12 } }, // MONSTER2B
};

const struct monster_change_data district3_monsters[] = {
    { 2, { 720, 80, 10 } }, // MONSTER3B
};

このように、変更されたモンスターをリストするだけで済みます。

于 2012-07-18T09:23:11.463 に答える
0

完全を期すために、私はltjaxが彼の答えを投稿する前に思いついたデザインを投稿しますが、私のものは劣っています。アプローチが異なるため、それでも他の人にとっては興味深いかもしれません。

テーブル自体にはほとんど意味がないため、ファクトリとテーブルを組み合わせます。テーブルの入力は、ファクトリのコンストラクタで行われます。このようにして、他のファクトリはコンストラクタを継承し、テーブルに変更を加えることができます。欠点は、すべてのファクトリが独自の完全なテーブルを作成するため、実行時に冗長データを格納することです。少なくともメンテナンスが容易になります。

これは、ヘルパーメソッドを移動し、それらを適切にカプセル化するために別のテーブルクラスに移動することで改善される可能性がaddありreplaceますremove。しかしmonster_factory_abstract、この場合、IMOは基本的に空になります。

class monster_factory_abstract
{
    private:
        monster_data* table; // or map with sequential indices
        int table_length;

        protected:
        // add monster to table
        void add(int HP, int strength, int speed, etc.); 

        // index starts with one to match monster names in this example
        void replace(int index, int HP, int strength, int speed, etc.); 
        void remove(int index); // nulls an entry
        void remove(int from, int to);

    public:
        virtual monster* create_random_monster();
}

class monster_factory_district1 : public monster_factory_abstract
{
    public:
        monster_factory_district1()
        {
            table_length = 0;

            add( 500, 20,  4 ); // monster1
            add( 550,  5, 12 ); // monster2
            add( 420,  8, 10 ); // monster3
            add( 310, 30,  7 ); // monster4
            // add 100 more monsters
        }
}; 

class monster_factory_district2 : public monster_factory_district1
{
    public:
        monster_factory_district2() : monster_factory_district1
        {
            replace( 2, 750,  5, 12 ); // MONSTER2B <<
            remove(81, 100);
        }
};

class monster_factory_district3 : public monster_factory_district1
{
    public:
        monster_factory_district3() : monster_factory_district1
        {
            replace( 3, 720, 80, 10 ); // MONSTER3B <<
            remove(5, 8);
            remove(91, 100);
        }
};

class monster_factory_district4 : public monster_factory_abstract
{
    public:
        monster_factory_district4() : monster_factory_abstract
        {
            table_length = 0;

            add( 100, 20, 10 ); // monster 401
            add( 200, 50, 20 ); // monster 402
            add( 300, 40,  5 ); // monster 403
            add( 400, 30, 30 ); // monster 404
        }
};
于 2012-07-18T10:46:14.360 に答える