Skip to content

Instantly share code, notes, and snippets.

@maumchaves
Last active April 19, 2018 04:54
Show Gist options
  • Save maumchaves/573ff4a3712402105a265cfc3ade7a24 to your computer and use it in GitHub Desktop.
Save maumchaves/573ff4a3712402105a265cfc3ade7a24 to your computer and use it in GitHub Desktop.
Functional Constructor Pattern: an inheritance pattern described by Douglas Crockford in his book "JavaScript: The Good Parts" (Ch.5).
"use strict";
/**
* var myConstructor = function(spec, shared) { ... };
*
* ~ The 'spec' object contains all of the information that the constructor
* needs to make an instance (it can also be a single value). Destructuring
* assignment can be used for more clarity about the excpected values.
*
* ~ The 'shared' object is a container of secrets that are shared by the
* constructors in the inheritance chain (optional). If not passed, it is made.
*/
var mammal = function ({name, saying}/* , { sharedValue = 'Shared' } = {} */) {
const that = {};
that.getName = () => name;
that.says = () => saying;
/**
* More securely, we can define the new public methods first as private
* methods, and then assign them to 'that'.
*/
// const getName = () => {
// return name;
// }
// that.getName = getName;
return that;
}
var cat = function ({name, saying}) {
saying = saying || 'meow';
const that = mammal({name, saying});
that.purr = () => 'r-r-r-r-r';
that.getName = () => (that.says() + ' ' + name + ' ' + that.says());
return that;
}
/**
* Accessing a super method
* ========================
*
* Let's extend the Object prototype with a 'superior' method. This function
* will invoke the original method even if the property is changed (overridden).
*
* 1. We extend the Function prototype to allow us to create methods in a safety
* way. Also, we will remove the ugliness of writing '.prototype' every time.
*
* 2. We use our new method 'method' to extend the Object prototype.
*/
Function.prototype.method = function (name, func) {
if (!this.prototype[name]) {
this.prototype[name] = func;
return this;
}
}
Object.method('superior', function (name) {
const method = this[name];
return function() {
return method.apply(this, arguments);
}
})
/**
* Some people claim that modifying objects that we don't own (define) is not
* a good practice. One reason is that it could bring compatibility issues
* with future versions (and bugs) if there are conflicts with the names of the
* properties they are using and the ones that we added. So an alternative to
* get the super method could be to implement a completly separate function.
*/
// function superior(thisArg, methodName) {
// const method = thisArg[methodName];
// return function() {
// return method.apply(thisArg, arguments);
// }
// }
var coolcat = function ({name, saying}) {
const that = cat({name, saying});
const superGetName = that.superior('getName');
that.getName = () => ('like ' + superGetName() + ' baby');
return that;
}
/**
* Objects composition
* ===================
*/
var logger = function (that) {
that.log = (message) => console.log('Log from logger:', message);
}
var loggerCat = function ({name, saying}) {
const that = coolcat({name, saying});
logger(that);
return that;
}
/**
* Let's try our implementation!
* =============================
*/
var myMammal = mammal({ name: 'Herb' });
var myCat = cat({ name: 'Henrietta' });
var myCoolcat = coolcat({ name: 'Bix' });
var myLoggerCat = loggerCat({ name: 'Charles' });
console.log(myMammal.getName());
console.log(myCat.getName());
console.log(myCat.purr());
console.log(myCoolcat.getName());
myLoggerCat.log(myLoggerCat.getName());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment