Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active December 14, 2015 04:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dfkaye/5025775 to your computer and use it in GitHub Desktop.
Save dfkaye/5025775 to your computer and use it in GitHub Desktop.
How many arguments should a constructor have?

What is the maximum number of arguments that a constructor should have?

The answer is 3. When the number of args exceeds 3 in a constructor, it's probably time to switch to a configuration object as a single argument. There are two-and-a-half benefits (note specificity):

  1. give your constructor a chance to set defaults if they're missing, verify that specific types are defined or that specific instances of types are defined

  2. OR allows you to assign a single property in the constructor to the config object and defer the integrity checks to other methods, if you wish, so you can add those later to the prototype rather than repeatedly modify the constructor (which can get quite big).

  3. makes mock arguments much easier to maintain in tests where you'll be driving your constructor's integrity checks first, before adding capabilities to the prototype or inheriting from another one.

In the following, all the checks are inline or-statements; if the first condition is false, the second is executed.

function TestDriver(config) {

    // if a property is not specified, supply defaults...

    this.string = config.string || "defaultString";
    this.regex = config.regex || "defaultRegex";

    // if a property is not specified but required, throw an error

    this.domNode = config.domNode || (throw new Error('domNode is required'));

     // if a property is required to be a specific type...

    this.fn = typeof config.fn === 'function' || (throw new Error('fn is required to be a function'));

     // if a property is required to be an instance of a specific type...

    this.complexObject = config.complexObject instanceof ComplexObject || (throw new Error('complexObject is required to a ComplexObject instance'));

    // etc.
};

That can be pushed out to a prototype method:

function TestDriver(config) {
    this.config = this.setup(config);
};

TestDriver.prototype.setup = function(config) {
    // do config checks as before - but use a new object to write to, rather than "this"
    var obj = {};

    obj.string = config.string || default;

    // etc.

    // return obj if we got this far
    return obj;
};

This will pass the argument checks:

var goodDriver = new TestDriver({
    string: 'Hit me',
    // regex is optional
    domNode: domNode,
    complexObject: complexObject,
    fn: fn
});

This will fail - complexObject is the wrong instanceof type

var badDriver = new TestDriver({
    string: 'Hit me again',
    // regex is optional
    domNode: domNode,
    complexObject: [],
    fn: fn
});

In the delegation to setup() method, you could even use a closure to keep others from hacking it accidentally on purpose,

function TestDriver(config) {

    var obj = this.setup(config);

    this.config = function () {
        return obj ;
    };
};

so that driver.config(), this.config() => always returns a compliant copy of the original config specifier.

It is for this last reason (proper inheritance of closures) that I devised my Constructor.extend() the way I did {@see constructor-api-proposal}.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment