1

I'm creating an application that allows a user to create widgets. There are several different types of widgets, and I have defined them using protypal inheritance. i.e.

//the base widget that all widgets inherit from
var Widget = function(){}        
Widget.prototype.someFunction = function(){}


//widget type A
var A = function(){}    
A.prototype = new Widget();


//widget type B
var B = function(){}    
B.prototype = new Widget();

I have discovered that it will be convenient to add a method on the base class that can create a new widget instance of the same type. i.e.

//the base widget
var Widget = function(){};        

Widget.prototype.clone = function(){
    switch(this.type){
         case 'A':
             return new A();
             break;
         case 'B':
             return new B();
             break;
         default:
             break;
    }
};

Which would allow me to get a new widget of the same type using the following code:

var widgetTypeA = new A();
var cloneOfWidgetTypeA = widgetTypeA.clone();

My concern is that the base widget now has to be explicitly aware of each of the types of widgets that inherit from it. Does this violate any principles of good OOP?

Mark Brown
  • 12,026
  • 8
  • 27
  • 32
  • Class-ception. Consider whatever solution you come up with to be maintainable. – Yzmir Ramirez Nov 11 '11 at 23:47
  • switch statements and OO programing are dual concepts. I highly recommend reading a bit about the [expression problem](http://c2.com/cgi/wiki?ExpressionProblem) – hugomg Nov 12 '11 at 05:05
  • In JavaScript, you can only define it using prototypal inheritance, JavaScript doesn't have a concept of object inheritance. – Daniel Dec 02 '18 at 16:05

4 Answers4

2
Widget.prototype.clone = function() {
  var constructor = window[this.type];
  return new constructor();
};

Assuming that all your subclasses are declared as globals of course.

But honestly I would out those sub classes in the Widget namespace, and access them through it rather than making everything global.

Widget.A = function(){};
Widget.A.prototype = new Widget();

Widget.prototype.clone = function() {
  var constructor = Widget[this.type];
  return new constructor();
};
Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • It is. But coffee script fixes it pretty well. http://jashkenas.github.com/coffee-script/#classes – Alex Wayne Nov 11 '11 at 23:55
  • actually, JavaScript's/ECMAScript's prototypal inheritance has more expressive power than classical inheritance - it's just a little bit more puzzling. – sled Nov 12 '11 at 00:24
  • @Squeegy They aren't globals, but I see where you're going and I like it. Thanks. – Mark Brown Nov 12 '11 at 19:26
2

Given that your constructors are globals, you could do something like:

var global = this;

Widget.prototype.clone = function() {
  if (global[this.type])
    return new global[this.type]();
};

Provided each instance has a type property whose value is the name of the constructor. Or you could fix the constructor property of constructor's prototype and do:

Widget.prototype.clone = function() {
    return new this.constructor();
};

function A() { };
A.prototype = new Widget();
A.prototype.constructor = A;

var a = new A();
var aa = a.clone();

However, that assumes that you don't have any parameters to pass. If you do have parameters to pass, then you likely have to know which type you are making and so can call the correct constructor anyway.

RobG
  • 142,382
  • 31
  • 172
  • 209
1

If ECMA5 is supported:

  • use Object.create(Object.getPrototypeOf(this));

If ECMA5 is not supported:

  • create an anonymous function
  • set the prototype of the anonymous function to the non-standard attribute this.__proto__

Example:

var Widget = function() { };

Widget.prototype.clone = function() {
  /*
   Non-ECMA5: 

   var newClone = function() {};
   newClone.prototype = this.__proto__;
   return new newClone(); 
  */

  // ECMA5
  return Object.create(Object.getPrototypeOf(this));
}


var A = function() { };
A.prototype = new Widget();
A.prototype.name = "I'm an A";

var B = function() { };
B.prototype = new Widget();
B.prototype.name = "I'm a B";

var x1 = new A();
var y1 = x1.clone();

console.log("y1 should be A: %s", y1.name);

var x2 = new B();
var y2 = x2.clone();

console.log("y2 should be B: %s", y2.name);
sled
  • 14,525
  • 3
  • 42
  • 70
  • Not all browsers that don't support ES5 support [__proto__](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/Proto). Note also that in JavaScript, [__proto__](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/Proto) is deprecated so do not use it. – RobG Nov 12 '11 at 03:52
1

The information you need is already available in the constructor property. However, overwriting prototype will lose it as I recently explained here.

Using my own class implementation for ECMAScript version 3 or version 5, your example would look like this:

var Widget = Class.extend({
    someFunction : function() {
        alert('someFunction executed');
    },

    clone : function() {
        return new this.constructor;
    }
});

var A = Widget.extend();

var B = Widget.extend({
    constructor : function(arg) {
        Widget.call(this); // call parent constructor
        this.arg = arg;
    },

    // override someFunction()
    someFunction : function() {
        alert('someFunction executed, arg is ' + this.arg)
    },

    // clone() needs to be overriden as well:
    // Widget's clone() doesn't know how to deal with constructor arguments
    clone : function() {
        return new this.constructor(this.arg);
    }
});

var a = new A;
var a2 = a.clone();
a2.someFunction();

var b = new B(42);
var b2 = b.clone();
b2.someFunction();
Community
  • 1
  • 1
Christoph
  • 164,997
  • 36
  • 182
  • 240
  • There is a lot for me to learn here. I didn't even know there was a constructor property. Anyway, you class implementation looks convenient. I will take a look at it this weekend. Thanks. – Mark Brown Nov 12 '11 at 19:28