Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying simple approach to OPP inheritance in Javascript (ES5)

Just for the sake of curiosity, I was playing with prototypal inheritance and OOP inheritance in Javascript. Most results involve emulating 'Class' and 'extends' concepts with functions, while others use the prototype and constructors.

I wrote this code:

function Warrior(weaponName) {
    var weapon = weaponName;
    this.getWeapon = function() {
        return weapon;
    };
    this.setWeapon = function(value) {
        weapon = value;
    };
    this.displayInfo = function() {
        return {
            "weapon": this.getWeapon(),
        };
    };
}

function Archer() {
    var accuracy = "86%";
    this.parent = Archer.prototype; // Inheritance workaround
    this.getAccuracy = function() {
        return accuracy;
    };
    this.setAccuracy = function(value) {
        accuracy = value;
    };
    this.displayInfo = function() {
        var form = this.parent.displayInfo();
        form.accuracy = this.getAccuracy();
        return form;
    };
}
Archer.prototype = new Warrior("bow");
var w = new Warrior("sword");
var a = new Archer();
console.log(w.displayInfo());
console.log(a.displayInfo());

I made this so when displaying the information from the Warrior class, it shows the object as

{ weapon: "sword" }

And when the information from Archer is shown, the object is:

{ weapon: "sword", accuracy: "86%" }

The "subclass" is taking information from the "superclass" and adding to it. Calling "getWeapon()" or "setWeapon" from Archer also works. The chain goes on without problems, even when I add a third class "Kyudoka" that extends "Archer" and has it's own properties as well.

But comparing to the more complex code I found while researching, I feel this could be a naive implementation (the "Inheritance workaround" line) and I'm missing something (considering that JS has a lot of subtlety).

This is a theorical question, I'm not using this code in any system.

like image 408
adrield Avatar asked Sep 27 '22 21:09

adrield


2 Answers

There are mainly 3 kinds of inheritance in javascript, according to the book Javascript the Good Parts: Pseudoclassical, Prototypal and Functional.

The one you just posted would fit under the Pseudoclassical inheritance, where you emulate a Class behaviour using constructor functions.

I find more useful and flexible the Functional pattern, which allows you to protect your variables (make them private).

var constructor = function (spec, my) {
  var that, other private instance variables;
  my = my || {};
  //Add shared variables and functions to my
  that = a new object;
  //Add privileged methods to that
  return that;
}

Prototypal is basically having your objects inherit directly from other useful object, which would be something like having them (the useful objects) as your new object constructor prototype.

Object.beget = function (o) {
  var F = function () {};
  F.prototype = o;
  return new F();
};

var a = {}
//Add shared variables to a
var b = Object.beget(a);
//Add new methods to b

That are many considerations to each of the patterns, for instance Crockford says in his book "The functional pattern has a great deal of flexibility. It requires less effort than the pseudoclassical pattern, and gives us better encapsulation and information hiding and access to super methods.", but I've also seen articles arguing the other way around, such as this http://bolinfest.com/javascript/inheritance.php

EDIT ------

In case you might want to know different aproaches to reaching super methods, in the Functional pattern you can do the following:

Function.prototype.method = function (name, func) {
  this.prototype[name] = func;
  return this;
};

Object.method('superior', function (name) {
  var that = this,
  method = that[name];
  return function ( ) {
    return method.apply(that, arguments);
  };
});

var archer = function (spec, accuracy) {
  var that = warrior(spec),
  super_displayInfo = that.superior('displayInfo');
  that.getAccuracy = function() {
    return accuracy;
  };
  that.setAccuracy = function(value) {
    accuracy = value;
  };
  that.displayInfo = function (n) {
    var form = super_displayInfo()
    form.accuracy = that.getAccuracy();
    return form;
  };
  return that;
};
like image 52
Roger Avatar answered Sep 30 '22 08:09

Roger


Put the functions on the prototype...

function Warrior(weaponName) {
    this.weapon = weaponName;
}

Warrior.prototype = {

    getWeapon : function() {
        return this.weapon;
    },

    setWeapon : function(value) {
        this.weapon = value;
    },

    displayInfo : function() {
        return { "weapon" : this.getWeapon() };
    }
};

//----------------------------------

function Archer(weaponName) {
    Warrior.call(this, weaponName);
    this.accuracy = "86%";
}

Archer.prototype = Object.create(Warrior.prototype);
Archer.prototype.constructor = Archer;

Archer.prototype.getAccuracy = function() {
    return this.accuracy;
};

Archer.prototype.setAccuracy = function(value) {
    this.accuracy = value;
};

Archer.prototype.displayInfo = function() { 
    return "weapon: " + this.getWeapon() + ", accuracy: " + this.getAccuracy();
};


//----------------------------------

var w = new Warrior("sword");
var a = new Archer("axe");
console.log(w.displayInfo()); // Object {weapon: "sword"}
console.log(a.displayInfo()); // weapon: axe, accuracy: 86%

Edit: fixed recursion

like image 44
Data Avatar answered Sep 30 '22 07:09

Data