Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I share "constructor" functionality in Crockford's new constructor pattern?

I've watched the video from this question on class-free OOP several times now, and I'm having trouble applying this to a real world example.

Crockford's new constructor pattern looks like this:

function constructor(spec) {
  let {member} = spec,
      {other}  = other_constructor(spec),
      method   = function () {
        // accesses member, other, method, spec
      };

  return Object.freeze({
      method,
      other,
  });
}

Where spec is an options hash and the resulting object exposes methods which close over all of the internal members. Ignoring the destructuring (as this can be done in long-form in present-day JS,) how do I apply this pattern in a real world example?

I currently have a library with a base class Module(model, id) where model is some bootstrapping data.

// constructor, original flavor
function Module(model, id) {
  this.id = id;
  this.model = model;
}

I then have several flavors of modules which inherit from this parent Module. Under Crockford's pattern, I would instead have this as a "constructor":

// constructor, Crockford's Cool Ranch
function module(spec) {
    let id = spec.id,
        model = spec.model;

    return Object.freeze({});
}

How do I use Crockford's pattern (which doesn't seem to use inheritance at all, but rather composing an object from multiple sources) to share this basic construction among multiple flavors of modules?

I know that id and model will become local variables in the "constructor" of each module; I am essentially asking how to avoid repeating model = spec.model for each flavor of module using Crockford's pattern.

like image 702
Mathletics Avatar asked Jan 10 '15 01:01

Mathletics


1 Answers

What Crockford calls “class-free inheritance” is actually still prototypal inheritance. Actually, there are two types of prototypal inheritance mechanisms:

  1. Prototypal inheritance via delegation (a.k.a. differential inheritance).
  2. Prototypal inheritance via cloning (a.k.a. concatenative inheritance).

An example of differential prototypal inheritance

This is how I traditionally write object-oriented JavaScript code:

var Aircraft = defclass({
    constructor: function (model, speed) {
        this.model = model;
        this.speed = speed;
    },
    describeAircraft: function () {
        alert("The " + this.model + " flies at " + this.speed + " speed.");
    }
});

var FighterAircraft = extend(Aircraft, {
    constructor: function (model, speed, radar) {
        Aircraft.call(this, model, speed);
        this.radar = radar;
    },
    describeFighterAircraft: function () {
        this.describeAircraft();
        alert("It has a " + this.radar + " radar signature.");
    }
});

var superFlanker = new FighterAircraft("Super Flanker", "Mach 2.25", "low");

superFlanker.describeFighterAircraft();
<script>
function defclass(prototype) {
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}

function extend(constructor, properties) {
    var prototype = Object.create(constructor.prototype);
    for (var name in properties) prototype[name] = properties[name];
    return defclass(prototype);
}
</script>

An example of concatenative prototypal inheritance

This is how Crockford advocates to write object-oriented JavaScript code:

var superFlanker = FighterAircraft({
    model: "Super Flanker",
    speed: "Mach 2.25",
    radar: "low"
});

superFlanker.describeFighterAircraft();
<script>
function Aircraft(spec) {
    var model = spec.model;
    var speed = spec.speed;

    function describeAircraft() {
        alert("The " + model + " flies at " + speed + " speed.");
    }

    return Object.freeze({
        model: model,
        speed: speed,
        describeAircraft: describeAircraft
    });
}

function FighterAircraft(spec) {
    var aircraft = Aircraft(spec);
    var model    = spec.model;
    var speed    = spec.speed;
    var radar    = spec.radar;

    function describeFighterAircraft() {
        aircraft.describeAircraft();
        alert("It has a " + radar + " radar signature.");
    }

    return Object.freeze({
        model: model,
        speed: speed,
        radar: radar,
        describeFighterAircraft: describeFighterAircraft
    });
}
</script>

Better concatenative prototypal inheritance using mixins

Crockford's method of concatenative prototypal inheritance has a lot of repetition. An alternative is:

var aircraft = mixin({
    describeAircraft: function () {
        alert("The " + this.model + " flies at " + this.speed + " speed.");
    }
});

var fighterAircraft = mixin(aircraft, {
    describeFighterAircraft: function () {
        this.describeAircraft();
        alert("It has a " + this.radar + " radar signature.");
    }
});

var superFlanker = fighterAircraft({
    model: "Super Flanker",
    speed: "Mach 2.25",
    radar: "low"
});

superFlanker.describeFighterAircraft();
<script>
function mixin() {
    var length = arguments.length;
    var index  = 0;

    while (index < length) {
        var properties = arguments[index++];
        for (var name in properties)
            constructor[name] = properties[name];
    }

    return Object.freeze(constructor);

    function constructor(object) {
        for (var name in constructor)
            object[name] = constructor[name];
        return Object.freeze(object);
    }
}
</script>

Using mixins without this

Yes, you can use mixins without using this. However, I don't see why you would want to:

var aircraft = mixin({
    describeAircraft: function (aircraft) {
        alert("The " + aircraft.model + " flies at " +
            aircraft.speed + " speed.");
    }
});

var fighterAircraft = mixin(aircraft, {
    describeFighterAircraft: function (fighterAircraft) {
        fighterAircraft.describeAircraft();
        alert("It has a " + fighterAircraft.radar + " radar signature.");
    }
});

var superFlanker = fighterAircraft({
    model: "Super Flanker",
    speed: "Mach 2.25",
    radar: "low"
});

superFlanker.describeFighterAircraft();
<script>
function mixin() {
    var length = arguments.length;
    var index  = 0;

    while (index < length) {
        var properties = arguments[index++];
        for (var name in properties)
            constructor[name] = properties[name];
    }

    return Object.freeze(constructor);

    function constructor(object) {
        for (var name in constructor) {
            var value    = constructor[name];
            object[name] = typeof value === "function" ?
                value.bind(null, object) : value;
        }

        return Object.freeze(object);
    }
}
</script>

Advantages of concatenative inheritance

There are many reasons to prefer composition over inheritance:

  1. Simple multiple inheritance.
  2. Faster property access.

The only disadvantage that I can think of is that if the prototype is modified then the change will not be reflected on its instances. However, there's no good reason to change the prototype anyway. Hence, my mixins are all frozen.

like image 152
Aadit M Shah Avatar answered Nov 05 '22 15:11

Aadit M Shah