JavaScript pattern for multiple constructors

138,428

Solution 1

How do you find this one?

function Foobar(foobar) {
    this.foobar = foobar;
}

Foobar.prototype = {
    foobar: null
};

Foobar.fromComponents = function(foo, bar) {
    var foobar = foo + bar;
    return new Foobar(foobar);
};

//usage: the following two lines give the same result
var x = Foobar.fromComponents('Abc', 'Cde');
var y = new Foobar('AbcDef')

Solution 2

JavaScript doesn't have function overloading, including for methods or constructors.

If you want a function to behave differently depending on the number and types of parameters you pass to it, you'll have to sniff them manually. JavaScript will happily call a function with more or fewer than the declared number of arguments.

function foo(a, b) {
    if (b===undefined) // parameter was omitted in call
        b= 'some default value';

    if (typeof(a)==='string')
        this._constructInSomeWay(a, b);
    else if (a instanceof MyType)
        this._constructInSomeOtherWay(a, b);
}

You can also access arguments as an array-like to get any further arguments passed in.

If you need more complex arguments, it can be a good idea to put some or all of them inside an object lookup:

function bar(argmap) {
    if ('optionalparam' in argmap)
        this._constructInSomeWay(argmap.param, argmap.optionalparam);
    ...
}

bar({param: 1, optionalparam: 2})

Python demonstrates how default and named arguments can be used to cover the most use cases in a more practical and graceful way than function overloading. JavaScript, not so much.

Solution 3

you can use class with static methods that return an instance of that class

    class MyClass {
        constructor(a,b,c,d){
            this.a = a
            this.b = b
            this.c = c
            this.d = d
        }
        static BAndCInstance(b,c){
            return new MyClass(null,b,c)
        }
        static BAndDInstance(b,d){
            return new MyClass(null,b, null,d)
        }
    }

    //new Instance just with a and other is nul this can
    //use for other params that are first in constructor
    const myclass=new MyClass(a)

    //an Instance that has b and c params
    const instanceWithBAndC = MyClass.BAndCInstance(b,c)

    //another example for b and d
    const instanceWithBAndD = MyClass.BAndDInstance(b,d)

with this pattern you can create multi constructor

Solution 4

Didn't feel like doing it by hand as in bobince's answer, so I just completely ripped off jQuery's plugin options pattern.

Here's the constructor:

//default constructor for Preset 'class'
function Preset(params) {
    var properties = $.extend({
        //these are the defaults
        id: null,
        name: null,
        inItems: [],
        outItems: [],
    }, params);

    console.log('Preset instantiated');
    this.id = properties.id;
    this.name = properties.name;
    this.inItems = properties.inItems;
    this.outItems = properties.outItems;
}

Here's different ways of instantiation:

presetNoParams = new Preset(); 
presetEmptyParams = new Preset({});
presetSomeParams = new Preset({id: 666, inItems:['item_1', 'item_2']});
presetAllParams = new Preset({id: 666, name: 'SOpreset', inItems: ['item_1', 'item_2'], outItems: ['item_3', 'item_4']});

And here's what that made:

presetNoParams
Preset {id: null, name: null, inItems: Array[0], outItems: Array[0]}

presetEmptyParams
Preset {id: null, name: null, inItems: Array[0], outItems: Array[0]}

presetSomeParams
Preset {id: 666, name: null, inItems: Array[2], outItems: Array[0]}

presetAllParams
Preset {id: 666, name: "SOpreset", inItems: Array[2], outItems: Array[2]}

Solution 5

Going further with eruciform's answer, you can chain your new call into your init method.

function Foo () {
    this.bar = 'baz';
}

Foo.prototype.init_1 = function (bar) {
    this.bar = bar;
    return this;
};

Foo.prototype.init_2 = function (baz) {
    this.bar = 'something to do with '+baz;
    return this;
};

var a = new Foo().init_1('constructor 1');
var b = new Foo().init_2('constructor 2');
Share:
138,428

Related videos on Youtube

codeholic
Author by

codeholic

Updated on June 10, 2021

Comments

  • codeholic
    codeholic about 3 years

    I need different constructors for my instances. What is a common pattern for that?

    • gblazex
      gblazex almost 14 years
      be a bit more specific please. you want constructors with different parameter sets?
    • Doug Hauf
      Doug Hauf almost 10 years
      Can you have more than one constructor in Javascript?
    • Moika Turns
      Moika Turns about 7 years
      Yes and no @DougHauf. Yes because the answer provided by bobince provides a way to deliver equivalent behaviour. No because if you wanted multiple distinct constructor functions (each sharing the same prototype object) how would the constructor property of the prototype object get set (since the constructor property can only point to one constructor function).
    • Andrew
      Andrew over 4 years
      All of these answers are old/not-ideal. I'm too lazy to type up an answer, but you can pass an object around to functions and constructors and then use the keys just like you would arguments, e.g.: function ({ oneThing = 7, otherThing = defaultValue } = {}) { }. The extra = {} I put in there is another trick I learned recently, in case you want the possibility of the user passing no object in at all and using all of the defaults.
    • Andrew
      Andrew about 4 years
      Followup: Here are some good ways to solve this problem: stackoverflow.com/a/32626901/1599699 stackoverflow.com/a/41051984/1599699 stackoverflow.com/a/48287734/1599699 I'm especially fond of the last one for true multiple-constructor-like support, using static factory functions as constructors (return new this();, return new this.otherStaticFactoryFunction();, etc.)!
  • Simon Groenewolt
    Simon Groenewolt almost 14 years
    Factory method feels like a good solution - just be sure to not confuse it with the use of a separate factory class, which probably is completely irrelevant in this use case.
  • Rob
    Rob about 12 years
    That link goes nowhere. There is no Javascript anchor on that page.
  • Alex Dean
    Alex Dean over 11 years
    Thanks, this is really nice. I would say the second option is useful not just when you have complex arguments, but also simple-yet-hard-to-distinguish arguments, e.g. supporting MyObj({foo: "foo"}) plus MyObj({bar: "bar"}). MyObj has two constructors - but both take one argument, which is a string :-)
  • isxaker
    isxaker over 10 years
    return new this(foobar); doesn't work. I change on return new Foobar(foobar); and all is work correct.
  • hofnarwillie
    hofnarwillie almost 10 years
    I don't get it. Can you add the code where you are actually using it? Are you going to have to call fromComponents every time? Because that's not truly a constructor, but rather a helper function. @bobince's answer seems more accurate then.
  • Doug Hauf
    Doug Hauf almost 10 years
    Can you add more to the example code for this particular example.
  • Doug Hauf
    Doug Hauf almost 10 years
    So basically what you are doing here is taking the object Foo and then calling the init_1 and init_2 parameters with the prototype functions. Should your init_1 and init_2 have the word function with them.
  • Doug Hauf
    Doug Hauf almost 10 years
    does there have to be a semi-colon after the } in the first Foo ().
  • laughingbovine
    laughingbovine over 9 years
    Thanks Doug, I made the change.
  • Jacob McKay
    Jacob McKay about 8 years
    I've rolled the same pattern in node.js too now with: npmjs.com/package/extend
  • Nathan Williams
    Nathan Williams over 7 years
    @hofnarwillie While not quite an exact constructor, by using a static method it performs fairly similar ie var foobarObj = Foobar.fromComponents(foo,bar); is all you need to create a new object with the alternative arguments.
  • Millie Smith
    Millie Smith over 7 years
    Are you sure this works? I wasn't able to chain new Foo() and the call to init together because I wasn't able to access properties on the objects. I had to run var a = new Foo(); a.init_1('constructor 1');
  • laughingbovine
    laughingbovine over 7 years
    @MillieSmith I'll admit I haven't written JS in a while now... but I just pasted this code into the Chrome JS console and the chain from new to init worked.
  • Moika Turns
    Moika Turns about 7 years
    Hi @DougHauf, Crockford's book 'JavaScript: The Good Parts' has a section on this named 'Object Specifiers', plenty of examples refer to it online.
  • Gabriel Simas
    Gabriel Simas about 7 years
    And Immutability? using prototype puts properties public, right?
  • Jacques
    Jacques over 6 years
    what's the benefit of putting these two init functions on the prototype chain instead of putting it directly in the Foo function?
  • ErroneousFatality
    ErroneousFatality about 6 years
    That is incorrect. Your second constructor definition overrides the first one, so when you're calling new Book() later on, you're calling the second constructor with all parameters' values set to undefined.
  • Blane Townsend
    Blane Townsend almost 5 years
    This is the best answer. The others resort to parsing arrays and doing a bunch of unnecessary work.
  • Amr Lotfy
    Amr Lotfy over 3 years
    "JavaScript will happily call a function with more or fewer than the declared number of arguments" this needs HAHA emoji, HAHAscript :D :D, I think SO should add emoji reactions to answers and questions.
  • Albert Bici
    Albert Bici about 3 years
    @word @word thank you for your help, i appreciate it!
  • Capi Etheriel
    Capi Etheriel over 2 years
    what if Foobaz extends from Foobar, wouldn't Foobaz.fromComponets create Foobar instances?