0

After adopting Prototype Pattern into a game, it pleasantly improved maintainability of my code.

However, I have started to fear that when the actual object become more detail-customized,
I tend to code the corresponding prototype to become more like a copy of the actual object.

(notice #v.2 in code)

class Prototype{ //aka. "fake object"
    public: Vec3 position;
    //bool isShootable;            //(#v.2)   //#X
    //float delayBetweenShoot;     //(#v.2)
    //float hp;                    //(#v.2)
};

class Actual{ //aka. "actual object"
    public: PhysicObject* rigidBody=nullptr;
    //bool isShootable;            //(#v.2)   //#X
    //float delayBetweenShoot;     //(#v.2)
    //float hp;                    //(#v.2)
};

int main(){ // simplify 
    Prototype prototype; 
    prototype.position = Vec3(1,2,3);
    //^ end prototype creation
    //v these lines are inside a function, but it simply works like this
    PhysicObject* phy=new PhysicObject(prototype.position);
    Actual actual;
    actual.rigidBody=phy;
    //actual.isShootable      =prototype.isShootable;         //(#v.2)  #Y
    //actual.delayBetweenShoot=prototype.delayBetweenShoot;   //(#v.2)
    //actual.hp               =prototype.hp;                  //(#v.2) 
    gameLogic.add(actual); //roughly speaking
}

There are two bad signals (#v.2):-
1. repetitive code in Prototype vs Actual (#X)
2. tedious copying of fields. (#Y)

Thus, I think something start to go wrong.
This pattern may naturally cause new maintainability issues.

In the real situation, the actual2 contains another actual1.
To adopt Prototype Pattern, I use a corresponding prototype2 inside another corresponding prototype1 :-

class Actual1{
    //... some fields e.g. A1 a1; A2 a2; A3 a3;
};
class Actual2{
    //... some other field e.g. B1 B2 B3
    Actual1 actual1;
};
class Prototype1{
    //... some fields e.g. very similar to A1 A2 A3
};
class Prototype2{
    //... some other field e.g. very similar to B1 B2 B3
    Prototype1 prototype1;
};

Question

  • (1) Is it common that Prototype Pattern create new maintainability issues?
  • (2) If yes, how to avoid it?
  • (3) If no, where am I wrong (especially the style of coding)? ... or this trend (of repetitive code) is not really an issue at all (i.e. I just panic.)?

My poor solution

I think it might be a good idea to encapsulate the repetitive part into a single structure named Settings.

class Settings{
    bool isShootable;           
    float delayBetweenShoot;    
    float hp;                   
};

class Prototype{ //aka. "fake object"
    public: Vec3 position;
    Settings settings;   //(#v.2)
};

class Actual{ //aka. "real object"
    public: PhysicObject* rigidBody=nullptr;
    Settings settings;   //(#v.2)
};

However, it might increase unfavorable cohesion (i.e. glue or strong relation) between Prototype and Actual. Thus, it may leader to another new maintainability problem again.

javaLover
  • 6,347
  • 2
  • 22
  • 67
  • Can you explain what you understand the "Prototype Pattern" to be? – Joel Cornett Dec 17 '16 at 03:50
  • @Joel Cornett IMHO, "Prototype Pattern" is to use "dummy & simplified & light-weight & easy-to-copy object" to cache setting of the object that I really want to create. I view it as a passable complex enum to different class and function - e.g. without need to create an actual game object. (Please fix me if it is wrong) :) – javaLover Dec 17 '16 at 03:53
  • 1
    Maybe I miss the point here, but I cannot see the benefit of all that. Not, compared to having a "template instance" (of same type) of the Actual object, which is then used to init new instances, which then are further configured by difference of by adding unique data to them. Also, the whole "Prototype" naming sounds to me a bit like javascript programmers going C++ ;) – BitTickler Dec 17 '16 at 04:30

2 Answers2

1

You can avoid the unnecessary duplication by subclassing your 'actuals' from the prototype. For example:

struct Prototype {
    bool isShootable = false;
    float delayBetweenShoot = DEFAULT_DELAY;
    float hp = DEFAULT_HP;
    Vec3 position = STARTING_POSITION;
    ...
};

struct Actual : Prototype {
    PhysicObject* rigidBody;
    Actual() : Prototype(), rigidBody(new PhysicObject(position)) {}
}

int main() {
    Actual actual;
    // now you can access these methods
    if (actual.isShootable) {
        ...
    }
    ...
}

Your intuition is correct in that by grouping 'common' fields together, you increase the coupling between those fields. In some sense there is a tradeoff between coupling and code duplication. It is up to you to determine what is an acceptable compromise that best suits your application.

Joel Cornett
  • 24,192
  • 9
  • 66
  • 88
  • Thank! I am impressed with your word "tradeoff". A little fear here : I think `Actual` is not `Prototype`, e.g. `Prototype` might have `position`, but `Actual` don't have `position`. For example, I may allow to set `position` only before construction of `Actual`. Thus, the inheritance `Actual : Prototype` will break (in sematic sense). Is there any cure to this? – – javaLover Dec 17 '16 at 04:18
  • 1
    Maybe pass position in to the constructor of Actual instead of maintaining it as a member of Prototype? – Joel Cornett Dec 17 '16 at 04:19
1

Probably using different types (with the ensuing double book keeping) is not the best interpretation of that design pattern.

See below for an approach which avoids the double book keeping and still holds the benefits of basic idea - to preconfigure a sample or template object once, then use that instance to initialize many other instances.

class A {
    int32_t id;
    bool shootable;
    bool moveable;
    bool destructable;
public:
    // A template instance specific constructor.
    A(bool shoot, bool move, bool destruct)
        : id(-1) 
        , shootable(shoot)
        , moveable(move)
        , destructable(destruct)
   {
   }
   // One or more "real" instance constructors.
   A(int32_t idval, const A& source)
        : id(idval)
        , shootable(source.shootable)
        , moveable(source.moveable)
        , destructable(source.destructable)
   {
   }
   // ...
};

int main(int argc, const char *argv[])
{
    A kind(true,false,true);
    A instance0(1,kind);
    A instance1(2,kind);
    return 0;
}

As a variation of the above idea, you could as well store a reference to the template instance and indeed use 2 types.

class UnitType
{
    int32_t hp;
    bool ranged;
    //...
};
class Unit
{
    int32_t id;
    const UnitType *type;
    // more data

public:
    Unit(int32_t idval, const UnitType* unitType)
    : id(idval)
    , type(unitType)
    {
    }
    //...
};

Of course, then the UnitType instance should not be writable by instances. And once, there is also e.g. currentHp, you have another form of duplication to handle. Plus, you need to ensure the life time for the UnitType template instance exceeds that of each Unit instance using it.

BitTickler
  • 10,905
  • 5
  • 32
  • 53
  • Thank +1. I did this few weeks ago, but later, I found that this approach will make `A` contain every fields of `Prototype` and `Actual` (union-way). It is doable, but quite messy. It is nice to know that this idea is same as expert's. :D – javaLover Dec 17 '16 at 04:52
  • Most likely, ``Actual`` in your case will also contain all the fields (``Prototype Union Actual = Actual``). Unless the prototype contained raw data which first needs transforming or aggregation... but that, I would probably factor out into a respective function anyway. And do it once for the prototype instead of each time for each new instance. – BitTickler Dec 17 '16 at 04:55