0

I'm currently facing a tricky problem which I am not able to solve in javascript. I have a function object Unit for example. This is the base object.

There are two function objects Tank and AirDefense which are prototypal inheriting from Unit

A "child" of Unit has two properties level (normal integer) and damagePerSecond (an array of integers where the index represents the level and the value the damage per second on that level).

level should be a "static" property, so whenever I change the level of an instance of AirDefense or Tank every instance of it that was previously created must be updated and apply the changed level

However there also should be an interface to change the level statically, so that I don't have to call the setLevel method on a concrete instance but rather on the function object itself.

The problem is that I am implementing a method called getDamagePerSecond() which returns the damage per second for the current level. But the current level of the concrete child object is not known by the parent.

I cannot use a static property on the base object because it would set the level for both objects Tank and AirDefense but the levels should be seperated for each of those child objects.

tl;dr: I want to use a child objects static property from the parent object, but I don't know the child in the parent object yet.

I'll provide what I have tried so far to clear up how these objects work together.

Unit

function Unit(){
    this.level;
    this.damagePerSecond;   
}
Unit.prototype.getDamagePerSecond = function(){
    return this.damagePerSecond[this.level];
};

Unit.prototype.getLevel = function(){
    return this.level+1;
}

Tank

function Tank(){

    this.level = Tank.level || 0;
    this.dps = [5,10,15];

}

Tank.prototype = new Unit();
Tank.setLevel = function(level){
    Tank.level = level-1;   
}

AirDefense

function AirDefense(){

    this.level = AirDefense.level || 0;
    this.dps = [1,5,9];

}

AirDefense.prototype = new Unit();
AirDefense.setLevel = function(level){
    AirDefense.level = level-1; 
}

Main (Correct Result)

Tank.setLevel(5);

var t = new Tank();
var a = new AirDefense();

console.log(t.getLevel()); //outputs 5
console.log(a.getLevel()); //outputs 1

Main (Wrong Result)

var t = new Tank();
Tank.setLevel(5);
var a = new AirDefense();

console.log(t.getLevel()); //outputs 1 - should be 5
console.log(a.getLevel()); //outputs 1

What I basically did was to fake static properties in the concrete objects. Level is a static property on my child object. During instantiation I have an additional instance property with the level which will be set to the value of the fake static property.

But there are several problems arising here:

  1. I can only set a level for an object type before it is instantiated. But I want to change the level of both, all objects that are already instantiated and those that will be in the future.

  2. Using this approach I need to implement a setLevel() method on all of my child objects. I think this is the opposite of dry.

  3. By providing a static interface it is impossible to directly message concrete instances that do already exist

If I used terms that are not usual when speaking about prototyping I'm sorry for that. I come from languages that have a classic OO-approach.

I'm not that familiar with prototypal inheritance just yet, so I hope somebody can give me a wink into the right direction.

thpl
  • 5,810
  • 3
  • 29
  • 43

1 Answers1

1

So level should be shared for all Tank instances? You can do something like this:

function Unit(){}
Unit.prototype.getLevel = function(){
    return this.level+1;
};
function Tank(){
    //level should be shared by all Tanks
    //  setting this.level shadows Tank.prototype.level
    //  so let's not do that
//    this.level = Tank.level || 0;
}
//not sure why you want a Unit instance to be
//  prototype of all Tanks
Tank.prototype = Object.create(Unit.prototype);
Tank.prototype.constructor=Tank;
Tank.prototype.level=5;
Tank.prototype.setLevel = function(level){
  //set Tank.prototype.level so it's shared
  //  for all tank instances
  Tank.prototype.level = level-1;   
}
//same for AirDefense
var t1 = new Tank();
var t2 = new Tank();
console.log("t1 level:",t1.level,"t2 level",t2.level);
console.log("t1 getLevel:",t1.getLevel(),"t2 getLevel",t2.getLevel());
t1.setLevel(11);
console.log("t1 level:",t1.level,"t2 level",t2.level);
console.log("t1 getLevel:",t1.getLevel(),"t2 getLevel",t2.getLevel());

Or have an object member on Tank.prototype and mutate that.

function Unit(){}
Unit.prototype.getLevel = function(){
    return this.shared.level+1;
};
Unit.prototype.setLevel = function(level){
    this.shared.level=level;
};
function Tank(){}
Tank.prototype = Object.create(Unit.prototype);
Tank.prototype.constructor=Tank;
Tank.prototype.shared={};
Tank.prototype.shared.level=5;
var t1 = new Tank();
var t2 = new Tank();
console.log("t1 level:",t1.shared.level,"t2 level",t2.shared.level);
console.log("t1 getLevel:",t1.getLevel(),"t2 getLevel",t2.getLevel());
t1.setLevel(11);
console.log("t1 level:",t1.shared.level,"t2 level",t2.shared.level);
console.log("t1 getLevel:",t1.getLevel(),"t2 getLevel",t2.getLevel());

More on constructor functions and prototype here: https://stackoverflow.com/a/16063711/1641941

UPDATE

Demonstrate inheriting instance specific and shared (prototype) members. A function is usually on the prototype because it does the same for all instances. A constructor can also initialize an instance specific member (such as damagePerSecond). No need to initialize it in both Tank and AirDefense as Unit can take care of it:

function Unit(args){
  this.damagePerSecond=args.damagePerSecond;
  //..bunch of other instance specific members
}

function Tank(){
  //Tank is a Unit so we can take all instance specific Unit members
  //inherit instance specific members from Unit
  Unit.call(this, {damagePerSecond:[2,3,4]});
}
//inherit shared members from Unit
//... Tank.prototype = Object.create(Unit.pro ... bla bla bla
var t = new Tank();
console.log(t.damagePerSecond);//=[2,3,4]

The args is an object that can be passed to the constructor I could pass it to Tank and Tank can pass it to Unit the following way:

function Tank(args){
  args=args||{};//don't worry if no args are passed
  args.damagePerSecond=args.damagePerSecond||[2,3,4];//passed arg or default
  //... and a bunch of other stuff
  Unit.call(this,args);
}

var t = new Tank();//all defaults
var t1 = new Tank({damagePerSecond:[7,8,9]});//super Tank

Using such a pattern is very helpful when you have functions calling other functions. When you define them strict like someFunction(who, what, when) and you'll have a long chain of functions you'll have a lot of typing to do when an extra variable named where needs to be added in the beginning but is only used at the end.

Most of this is covered by the link I posted before.

Community
  • 1
  • 1
HMR
  • 37,593
  • 24
  • 91
  • 160
  • Thanks for you answer and especially your solution! It worked like a charm and you saved me a lot of trouble :) You commented one thing there `//not sure why you want a Unit instance to be prototype of all Tanks`. I thought that'd be a good idea, since all Units (Tank & AirDefense) should share common methods such as `getLevel()` and later `getDamagePerSecond()`. Did I choose a wrong approach here? – thpl Jun 04 '14 at 07:19
  • @ThomasDavidPlat Unit may have shared members on Unit.prototype and behavior that is in both Tank and AirDefinse but should be instance specific. In Tank and AirDefence you can copy this instance specific code but you can also take advantage of these common instance specific members by putting them in the Unit constructor and call them with `Unit.call(this,args)` If you create a Unit instance for Tank prototype then there are instance specific members of Unit on Tank.prototype. See the link I posted how strange that can be when instances start mutating those. – HMR Jun 04 '14 at 09:14
  • 1
    @ThomasDavidPlat Updated my answer to demonstrate how Unit can take care of DamagePerSecond assuming this is an instance specific member that all Unit instances should have. – HMR Jun 04 '14 at 09:30
  • Awesome! Thanks for clarification. I wish I could upvote 10 times! – thpl Jun 04 '14 at 09:47