2012-07-18 75 views
3

想象一下下面的情况:继承struct数据

我想创建各种怪物工厂。这些怪物工厂根据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,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或使用全新声明的区域?

奖励积分,如果您的设计确保怪物统计列表的每个变体只能有一个工厂实例。

+0

看起来很浪费和多余,但最简单的方法就是像现在一样将它保留为几个(可能是重复的)表格。另外,你可能希望将这些表格保持在外部,例如在例如一个或多个文件,并在启动程序时读取它。 – 2012-07-18 08:56:13

+0

@JoachimPileborg我认为这种可能性,但我期待大约10个额外的区域从另一个区域继承他们的表格。如果一个怪物的统计信息发生变化并需要传播到其他文件,采用简单方法并将数据存储在文件中可能会成为维护噩梦。此外,玩家将可以稍后编辑这些文件。 ;-) – 2012-07-18 09:13:53

+0

你的工厂在它的consructor中只需要一个'monster_data',是否意味着需要'monster_data *'(和一个长度?),以便它可以从数组中挑选一个随机怪物? – 2012-07-18 09:25:33

回答

2

这可以优雅地由decorator pattern通过在每一层的变化装潢的“默认”表来解决:

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)。为了支持省略数据,您可以添加另一个重新映射索引的装饰器“图层”(例如,maps [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指针/引用。

0

我想要一个怪物时,我会有一家工厂通过这个地区。 然后,我可以这样做(只有伪代码)

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; 
} 
+0

一个问题是区域2省略了某些'monster_data'条目,区域3也是如此。按照您的方法,您必须重试,直到找到区域2的有效怪物。太糟糕了,C++不支持UnrealScripts [默认属性](http://wiki.beyondunreal.com/Legacy:Default_Properties)。 – 2012-07-18 08:56:07

1

你留着用的是“继承”,但我绝对不会考虑这里继承,你只有一个类型的行为,即一种类型的工厂类,你只是想用不同的数据初始化工厂。

我将有一个大的阵列的所有不同monster_data值:

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类型只是thr ee整数应该没问题。如果它是一个更大的类,那么你可以制作data_seq a vector<const monster_data*>,因此它只保存指向all_data阵列元素的指针。这避免了复制monster_data对象,它们只是住在主数据库all_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 }; 

然后用这些数组中的一个(及其长度)构造一个工厂。工厂可以从列表中选择一个索引,然后使用它索引到all_data以获得monster_data

+0

+1很难决定是否接受你的答案或是ltjax的答案。由ltjax使用的OO做出了微小的差异。我将尝试在装饰器中进行映射的序列。 – 2012-07-18 10:26:56

0

一个解决方案可能是拥有一个包含“标准”怪物数据的基表,然后对于每个分区你有一个只包含修改过的怪物列表的表格。

是这样的:

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 
}; 

这种方式,你只需要列出改变怪物。

+1

你还需要一个特殊的“无怪物”值来表示区域2只有80个怪物,而区域3没有来自基本阵列的怪物5,6或7 – 2012-07-18 09:24:55

+0

@JonathanWakely我可能会解决它通过不断说出每张桌子上有多少个怪物。 – 2012-07-18 09:27:11

0

为了完整起见,我会发布我在ltjax发布他的答案之前提出的设计,虽然我的实力很差。由于它有不同的方法,所以其他人可能会感兴趣。

它把工厂和桌子结合在一起作为桌子本身意义不大。 表格的填充工作由工厂的构造函数完成。这样,其他工厂可以继承构造函数并对表进行更改。 缺点是每个工厂都创建自己的全表,因此在运行时存储冗余数据。至少维护变得更容易。

将辅助方法add,replaceremove移动到单独的表类中以正确地封装它们可能会改进此操作。但在这种情况下,国际海事组织(IMO)monster_factory_abstract基本上是空的。

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 
     } 
}; 
1

二进制存储数据往往是不好的做法,不结垢,尤其是如果它会是一个巨大的数据量。定义自己的支持简单数据继承的迷你语言,然后将其解析为包含unordered_map的类,应该不会有太大麻烦。如果您需要,您还可以实现简单的数据共享和更复杂的财产系统。