Search code examples
c++factory-pattern

Inherit from struct data


Imagine the following situation:

I want to create various monster factories. These monster factories create monsters based on the data provided by a struct array. The monsters only differ in these stats, therefore creating a subclass for each monster is overkill.

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

A class monster can handle all the behavior of a monster based on a monster_data:

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

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

So far, so good. Now I have a class monster_factory that creates monsters based on a hard coded monster_data array:

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

My problem is that I have to support several monster_factories for several districts with with minor differences in the lists:

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

Instead of copying and pasting the array data, I would like to somehow inherit from it, because the data stays mostly the same between the various versions. Copying the whole struct array declaration just to have a slightly different variant seems like the wrong way. Too bad that district 2 and 3 just don't append data, they modify and omit existing entries. Of course they change more than one monster, too.

In addition changes on the monster data of district 1 should apply to district 2 and 3 as well.

Another problem is that there are districts that will have monster data completely unrelated to districts 1,2 and 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
};

Now to the question: How can the outlined design be changed, so that redundant monster_data declarations are avoided and that districts can be added that either derive their monster_data from an existing declaration or use a completely new one?

Bonus points, if your design ensures that there can only be one factory instance for every variant of the monster stats list.


Solution

  • This can be solved elegantly by the decorator pattern by decorating the "default" table with the changes in each layer:

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

    You would then add all "changes" in higher districts to the OverlayData and let the OverlayMonsterTable refer to the default table (district1).

    To support omitting of data, you can either add another decorator "layer" that remaps indices (for example, maps [0...80] to [0...10], [30...100]), or integrate this functionality into the existing OverlayMonsterTable. Either way, you have full flexibility. For example:

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

    Your factory would just take a MonsterTable pointer/reference.