3
\$\begingroup\$

I have made the following example and want to know if it properly represents polymorphism in JavaScript.

var customShape = function(){
    this.sides = 4;
    this.length = 5;
    this.perimeter = 4 * this.length; 
};

customShape.prototype.noOfSides = function(){
    return ("Total No of Sides = " + 4);
}

var rect = new customShape();
var square = new customShape();
var triangle = new customShape();
triangle.noOfSides = function(){
    return ("Total No of Sides = " + 3);
}

console.log(rect.noOfSides());
console.log(square.noOfSides());
console.log(triangle.noOfSides());

My intention was to make a function noOfSides() which works for different objects.

References:

  1. What is polymorphism, what is it for, and how is it used?
  2. What is polymorphism? Explain with an example?
  3. What is polymorphism in Javascript?
  4. Basic javascript inheritance and polymorphism
\$\endgroup\$
2
\$\begingroup\$

No that is not an example of polymorphism as each instance is the same object. Just giving them different names does not change the type.

Update. I overlooked the assignment of a new function to the object named triangle. That also does not constitute an example of polymorphism.

Super Polymorphism *

(*) (not a real term in CS as far as I know)

There are a wide variety of ways to define objects and functions for the objects. It is not the object type that determines the behaviour provided by shared functions (more apt than calling them polymorphic)

The requirement be only that they provide the correct set of properties and behaviours for shared functions to operate on. In a sense the ultimate form of polymorphism, truly independent of type and thoroughly unsafe as is any super power if you don't learn to use it with care.

Examples of polymorphic like JS.

Personally I don't like to use the term polymorphic in JS as JS does not really define an interface, however as shown below you could consider polymorphic like behaviours

function position(x,y){
   this.x = y;
   this.y = y;
   // The next function if considered as an interface to the object 
   // can be thought of as polymorphic as it is independent of the
   // object type.
   this.translate = function(x,y){
      this.x += x;
      this.y += y;
   }
   // A more generic type of polymorphic interface
   // will work for any object. If it has a w,h property it has
   // 4 sides if not it has 0
   this.noSides = function(){
      // deductive property looks at objects properties to 
      // workout the number of sides.
      if(this.w && this.h){ return 4 }
      return 0; // Circle has no sides
   }
}

function Box(x,y,w,h){
   this.w = w;
   this.h = h;
   position.call(this, x, y);
   this.scale = function(scale){
       this.w *= scale;
       this.h *= scale;
   }       
}    
function Circle(x,y,r) {
   this.r = r;
   position.call(this, x, y);
   this.scale = function(scale){
       this.r *= scale;
   }
}

// though not a polymorphic object interface it is a function
// that only requires properties not an object type so is
// a form of a polymorphic interface
function scalePos(obj, scale){
    obj.x *= scale;
    obj.y *= scale;
    obj.scale(scale); // call object specific scale
}



var box = new Box(10,10,100,100);
var circle = new Circle(10,10,100);

box.translate(10,10);
circle.translate(10,10);
scalePos(box,10);
scalePos(circle,10);

Inferred object type

Or the ad-hoc construction approch can be called polymorphic using a factory function (or frankensteiner as I like to call from time to time)

Objects are created from parts Each part on its own is barely a usable object, only together do they become a usable object (alive). The resulting objects are in a sense typeless objects that share polymorphic functionality.

const box = {
   w : 0,
   h : 0,
};
const circle = {
   r : 0,
};
const position = {
   x : 0,
   y : 0,
};

 // polymorphic functions
const moveable = { 
   translate(x, y) {
       this.x += x;
       this.y += y;
   }
};
const growable = {
   scale(scale) {
       this.x += x;
       this.y += y;
       if (this.r) { this.r *= scale }
       if (this.w) { this.w *= scale }
       if (this.h) { this.h *= scale }
   },
};
const definable = {
   get sides() {
       return this.r !== undefined ? 0 : 4;
   },
};

// define common set of behaviours
const polySet = [moveable, growable, definable];

// factory function called frankensteiner because you have to 
// include the object's state (zap the life into the object) as the
// last part of the construction.
function frankensteiner(...parts) {
    return Object.assign({}, ...parts);
}

// types is inferred by property and functionality and is completely abstract
var point = frankensteiner(position, moveable ,{x: 10, y: 10});
var box1 = frankensteiner(box, position, ...polySet,{x: 10, y: 10, w: 100, h: 100});
var circle1 = frankensteiner(circle, position, ...polySet,{x:10,y:10,w:100,h:100});
var circleBox = frankensteiner(circle, box, position, ...polySet, {x: 10, y: 10, w: 100, h: 100, r: 100});

//point has not scale
box1.scale(2);
circle1.scale(2);
circleBox.scale(2);

point.translate(2,2);
box1.translate(2,2);
circle1.translate(2,2);
circleBox.translate(2,2);

You can make the object factory smart and add properties and functions depending on the parts its made of.

Or you can use the class syntax or direct to prototype and have a stricter form of inheritance via prototypes. Though closer to real polymorphism it still is not class safe.

No class safety

Without the strict classing of languages like C++ and Java there is no real object type. At any stage I can change an object

box.name = "Bob";

And in the strict class definition it is no longer a true instanceOf Box, it has been mutated. The mutation can make polymorphic like interfaces unsafe

box.x = "center";

It has meaning but the functions that rely on x being the correct type (Number) will fail. In JS the mutated box is still an instanceof Box and to be safe you would have to vet each property in turn. Not at all a practical form of polymorphism.

\$\endgroup\$
2
\$\begingroup\$

[I] want to know if it properly represents PolyMorphism in JavaScript?

Your code could be considered a representation of polymorphism when considering that the noOfSides() function was changed for a single instance of the customShape type, but that doesn't really apply to all objects of that type. Traditionally in Object-Oriented programming, sub-typing would be used to create sub-classes that inherit the properties and methods of the base class.

You created instances of customShape, but didn't really make any objects that inherit that functionality. For instance, you made the triangle instance and added a function (like a method) to that one instance:

var triangle = new customShape();
triangle.noOfSides = function(){
    return ("Total No of Sides = " + 3);
}

Looking at the fourth reference you included, look at the Padawan inheritance example

var jedi = function(name, age) {
    Padawan.call(this, name, age);
};
jedi.prototype = Object.create(Padawan.prototype);

Similarly, you could make a constructor for a triangle. The customShape constructor doesn't appear to accept any parameters, though it could accept the number of sides, in which case the triangle constructor could pass that in as an argument to the call to customShape.call(), but for now, the sides property can be set after that call.

function triangle() {
    //initialization code
    customShape.call(this);
    this.sides = 3;
}

And then set the prototype for the triangle to an object with the customShape prototype:

triangle.prototype = Object.create(customShape.prototype);

Then you don't need to add an additional noOfSides() function for the triangle. See a demonstration below. Note that I updated the noOfSides() method to utilize this.sides instead of a hard-coded value (like Matt Fletcher mentioned in a comment on your SO question, and also did in the jsbin example he made and linked in another comment).

var customShape = function(){
    this.sides = 4;
    this.length = 5;
    this.perimeter = 4 * this.length; 
};

customShape.prototype.noOfSides = function(){
    return ("Total No of Sides = " + this.sides);
}

var rect = new customShape();
var square = new customShape();
var triangle = function() {
    customShape.call(this);
    this.sides = 3;
}
triangle.prototype = Object.create(customShape.prototype);

var tri = new triangle();
console.log('rect: ', rect.noOfSides());
console.log('square: ', square.noOfSides());
console.log('triangle: ', tri.noOfSides());

Matt Fletcher has good points in his comment at 2017-11-29 12:37:29Z:

@Deadpool Fine. This is a somewhat improved version: jsbin.com/jazasav/edit?html,js,console ... but it still makes little sense, because "length" can differ on all sides. Also these shapes and their values don't follow such simple mathematical rules. In a real world example you'd need to be able to pass in much more complex data and get it to process it in a more complex way. Perhaps use the whole "Animal" inheritance pattern to better explain it, if that's what you're planning on doing. I just would like to know what you're actually trying to gain from generating polymorphic obj

And Matt's jsbin includes an ES2015 example, which should be fsmiloar to OOP users in other languages like C++, Java, PHP, etc.

// ES6 style

class CustomShape {
  constructor(sides, length) {
    this.sides = sides;
    this.length = length;
    this.perimeter = sides * length;
  }
  noOfSides() {
    return ("Total No of Sides = " + this.sides);
  }
}

class Square extends CustomShape {
  constructor(length) {
    super(4, length);
  }
}

const mySquare = new Square(5);
console.log(mySquare.noOfSides());

\$\endgroup\$
  • \$\begingroup\$ This answer is pretty good. Who dares to downvote it? ;-| \$\endgroup\$ – t3chb0t Dec 4 '17 at 23:32
  • 1
    \$\begingroup\$ ¯\_(ツ)_/¯ I have this strange hypothesis that some user is haunting me by downvoting my answers periodically... a few weeks ago, I had a couple days with +10 on ~9-10 answers... [plays X files theme] \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Dec 4 '17 at 23:36
  • \$\begingroup\$ You might be right... I just checked and I seem to be mostly the only upvoter of your reviews which I really like and learn a lot from them ;-D but I cannot vote too much... the system has reverted it all once ;-] \$\endgroup\$ – t3chb0t Dec 4 '17 at 23:47
  • \$\begingroup\$ Thanks! more evidence of manipulation \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Dec 5 '17 at 18:10
1
\$\begingroup\$

Your snippet comes close to demonstrating polymorphism, but none of your calls to noOfSides() are polymorphic.

console.log(rect.noOfSides());
console.log(square.noOfSides());
console.log(triangle.noOfSides());

rect has only one shape, so rect.noOfSides() always calls the same method with the 4 sides. It's the same with square and triangle.

For polymorphism, we need to polymorph something, to give it more than one shape, like a variable that can be a rectangle or a triangle.

var polylog = function(s) {
    console.log(s.noOfSides());
};
polylog(rect);
polylog(square);
polylog(triangle);

In polylog, the argument s can have two different shapes; it polymorphs from the 4-sided shape to the 3-sided shape, so s.noOfSides() can call two different functions. This demonstrates polymorphism.

\$\endgroup\$
0
\$\begingroup\$

You didn't specify in your question that you can't use ES2015. Here is an example with the class keyword introduced in ES2015. With classess the code is cleaner and is similiar to OO languages.

Important: Classes are not classes like in other OO languages. The class keyword is just a wrapper around the protoype pattern. More info about classes here.

class Shape {
    constructor() {
    }
    getSides() {
        return `Base class returns: ${this.sides}`;
    }
}

class Square extends Shape {
    constructor() {
        super();
        this.sides = 4;
    }
}

class Triangle extends Shape {
    constructor() {
        super();
        this.sides = 3;
    }
    getSides() {
        return `Triangle class returns: ${this.sides}`;
    }
}


var square = new Square();
console.log('square', square.getSides()); // square Base class returns: 4

var triangle = new Triangle();
console.log('triangle', triangle.getSides()); // triangle Triangle class returns: 3
\$\endgroup\$
  • 1
    \$\begingroup\$ You've basically supplied an alternate solution, is there any way you can actually review the code. On code review alternate solutions are not the best answers. \$\endgroup\$ – pacmaninbw Nov 29 '17 at 16:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.