koramaiku
koramaiku

Reputation: 358

Prototypal Inheritance Multiple Instances of Objects

I'm trying to get the grips of using prototypal inheritance. Here is the code I have below. It is based on the book "Object -Oriented Javascript" by Stoyan Stefanov, with just a few modifications.

Basically I have an Athlete object that extends a Person object. I have created 3 objects. Bob is a Person, while Billy Jean and Steve are Athletes. I added Bob, Billy Jean and Steve in that particular order. I invoked say() and run() functions and getSpeed() and jump(), for the Athlete objects, in all 3 objects in this particular order: Bob, Billy Jean and Steve.

Here is the code below.

<script type="text/javascript">

    function clone(o) {
    var n;
    function F(){};
    F.prototype = o;
    n = new F();

    return n;

   }

    /* Uber is equivalent to the extends method */
   function uber(parent, child) {
     var n = clone(parent);
     n.uber = parent;

   for (var i in child) {
      n[i] = child[i];
   }
   return n;
}

var Person = {
   initialise: function(name)
   {   
      this.name = name;
         },
   say: function()
   {
    console.log('My name is ' + this.name + '. I am a person'); 
   },
   run: function(){
    console.log('I have run 5km');
   },
   jump: function() {
      console.log('I have jumped for joy!');
   }
 };


 var Athlete = {
     initialise: function(name,speed) {
         this.speed = speed; 
         //uber is the parent
    this.uber.initialise(name);
},
    say: function() { console.log('My name is ' + this.name + '. I am an athlete');},
    run: function() { console.log('I have run 20km'); this.jump()},
    getSpeed: function() {console.log('My Speed is: ' + this.speed + 'km Hour');}
  }


  var Athlete = uber(Person, Athlete);

  console.log("Hello, Starting Test...");
  var bob = clone(Person); 
  bob.initialise('Bob');
  bob.say();
  bob.run();

  console.log("Adding Billy Jean...");
  var billyJean = clone(Athlete);
  billyJean.initialise('Billy Jean', 15);

 console.log("Adding Steve...");
 var steve = clone(Athlete);
  steve.initialise('Steve', 25);

 console.log("Asking Billy Jean...");
 billyJean.say();
 billyJean.run();
 billyJean.getSpeed();

 console.log("Asking Steve...");
 steve.say();
 steve.run();
 steve.getSpeed();

</script>

However, when I run the code, although I invoke the functions for Billy Jean first, Steve's properties pop up twice, meaning Steve replaced Billy Jean. Output shown below.

Hello, Starting Test...
My name is Bob. I am a person
I have run 5km
Adding Billy Jean...
Adding Steve...
Asking Billy Jean...
My name is Steve. I am an athlete
I have run 20km
I have jumped for joy!
My Speed is: 15km Hour
Asking Steve Tran...
My name is Steve. I am an athlete
I have run 20km
I have jumped for joy!
My Speed is: 25km Hour

I was just wondering if there is a way to separate Billy Jean and Steve So that I get both their details instead of Steve's details twice?

If it's impossible then what alternative can I use instead to solve this problem? Any solution or help would be a great help.

Upvotes: 0

Views: 364

Answers (2)

Stephen
Stephen

Reputation: 5470

Here's your issue. "this.uber" is referencing an object that is shared between steve and billyJean. Objects are passed around by reference, so that's why this is happening. Try replacing

this.uber.initialise(name); 

with

this.uber.initialise.call(this, name); 

(In case you aren't aware, 'call' and 'apply' make 'methods' run in different scopes). I don't have any real background on your experience, so hopefully this next ramble isn't insulting.. but, a few thoughts. I've rarely seen javascript done like this. Most of the time it has been something more along the lines of...

var bob = new Person(); 
bob.initialise('Bob');
bob.say();
bob.run();

Why the 'clone'? Did you want a purist approach? If so, the 'clone' isn't any better - you're still calling 'new' and doing a little magic. A purist approach would be more of.. (pardon the jQuery.. being lazy)

Object.prototype.clone = function () {
  return $.extend(true, {}, this);
};

Object.prototype.improve = function (extras) {
  var spawn = $.extend(true, {}, this, extras);
  spawn.uber = this;
  return spawn;
};

var Person = {
  initialise: function(name) {   
    this.name = name;
  },
  say: function() {
    console.log('My name is ' + this.name + '. I am a person'); 
  },
  run: function(){
    console.log('I have run 5km');
  },
  jump: function() {
console.log('I have jumped for joy!');
  }
};

var Athlete = Person.improve({
  initialise: function(name,speed) {
    this.speed = speed; 
    //uber is the parent
    this.uber.initialise.call(this, name);
  },
  say: function() { console.log('My name is ' + this.name + '. I am an athlete');},
  run: function() { console.log('I have run 20km'); this.jump()},
  getSpeed: function() {console.log('My Speed is: ' + this.speed + 'km Hour');}
});

var bob = Person.clone(); 
bob.initialise('Bob');
bob.say();
bob.run();

console.log("Adding Billy Jean...");
var billyJean = Athlete.clone();
billyJean.initialise('Billy Jean', 15);

console.log("Adding Steve...");
var steve = Athlete.clone();
steve.initialise('Steve', 25);

console.log("Asking Billy Jean...");
billyJean.say();
billyJean.run();
billyJean.getSpeed();

console.log("Asking Steve...");
steve.say();
steve.run();
steve.getSpeed();

This works just as well. Note that I'm still using 'call' here - it's because the original is passed by reference. You could undo that.. make it pass a clone.. but really, it's a waste of memory and cycles 90% of the time.

So, there's my best 'purist' approach! I personally don't like this way, as I see 'steve' and 'billyJean' as 'new' Athletes - not clones of an Athletes object - so I'd be more inclined to use a pattern that lets me do 'steve = new Athlete('Steve');'

My two cents, hope it helps.

Upvotes: 0

MBO
MBO

Reputation: 31015

This particular line is problematic in your example in Athlete.initialize:

//uber is the parent
this.uber.initialise(name);

With this call, you call initialize on object represented by uber, which is shared among your athlets. Change this to:

this.uber.initialize.call(this, name);

to call initialize from uber on your actual object (this) passing additional paremeters.

Upvotes: 1

Related Questions