Skip to content

Instantly share code, notes, and snippets.

@pluma
Last active December 26, 2015 21:28
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 pluma/7215849 to your computer and use it in GitHub Desktop.
Save pluma/7215849 to your computer and use it in GitHub Desktop.
ES-harmony multiple inheritance.
require('harmony-reflect');
var uniq = require('uniq');
var arrp = Array.prototype;
function merge(obj, src) {
Object.keys(src).forEach(function(name) {
var value = src[name];
if (obj.hasOwnProperty(name)) {
merge(obj[name], value);
} else {
var proto = Object.getPrototypeOf(value);
if (proto === Array.prototype) {
obj[name] = [].concat(value);
} else if (proto === Object.prototype || proto === null) {
obj[name] = merge(Object.create(proto), value);
} else {
obj[name] = value;
}
}
});
return obj;
}
function ext(obj) {
var sources = arrp.slice.call(arguments, 1);
sources.forEach(function(src) {
src && Object.keys(src).forEach(function(name) {
obj[name] = src[name];
});
});
return obj;
}
function chained(self, fn) {
return function() {
fn.apply(this, arrp.slice.call(arguments, 0));
return self;
};
}
function golem(initial) {
var proto = ext(Object.create(null), initial);
var parents = [];
var defaults = {};
var initializers = [];
function factory(state) {
var instance = Object.create(proto);
var lookups = [instance].concat(parents.map(function(proto) {
return proto.create();
}));
ext(merge(instance, defaults), state);
initializers.forEach(function(initializer) {
initializer.call(instance);
});
return Proxy(instance, {
getOwnPropertyDescriptor: function(target, name) {
for (var i = 0; i < lookups.length; i++) {
var lookup = lookups[i];
if (lookup.hasOwnProperty(name)) {
return Object.getOwnPropertyDescriptor(lookup, name);
}
}
return undefined;
},
getOwnPropertyNames: function(target) {
return uniq(lookups.map(function(lookup) {
return Object.getOwnPropertyNames(lookup);
}).reduce(function(a, b) {
return [].concat(a, b);
}, []).sort());
},
deleteProperty: function(target, name) {
lookups.forEach(function(lookup) {
delete lookup[name];
});
return true;
},
has: function(target, name) {
return lookups.map(function(lookup) {
return name in lookup;
}).reduce(function(a, b) {
return a || b;
}, false);
},
hasOwn: function(target, name) {
return lookups.map(function(lookup) {
return lookup.hasOwnProperty(name);
}).reduce(function(a, b) {
return a || b;
}, false);
},
get: function(target, name) {
for (var i = 0; i < lookups.length; i++) {
var lookup = lookups[i];
if (name in lookup) {
return lookup[name];
}
}
return undefined;
},
enumerate: function(target) {
return uniq(lookups.map(function(lookup) {
var keys = [];
for (var key in lookup) {
keys.push(key);
}
return key;
}).reduce(function(a, b) {
return [].concat(a, b);
}, []).sort());
},
keys: function(target) {
return uniq(lookups.map(
Object.keys.bind(Object)
).reduce(function(a, b) {
return [].concat(a, b);
}, []).sort());
},
});
}
proto.$golem = factory;
return ext(factory, {
create: factory,
mixin: chained(factory, arrp.push.bind(parents)),
extend: chained(factory, ext.bind(null, proto)),
initializer: chained(factory, arrp.push.bind(initializers)),
defaults: chained(factory, ext.bind(null, defaults)),
$parents: parents,
isInstance: function(obj) {
var $golem = obj.$golem;
if (!$golem) {
return false;
}
var parents = [$golem];
while (parents.length) {
$golem = parents.shift();
if ($golem === factory) {
return true;
}
arrp.push.apply(parents, $golem.$parents);
}
return false;
}
});
}
module.exports = ext(golem, {
compose: function() {
var g = golem();
g.mixin.apply(g, arrp.slice.call(arguments, 0));
return g;
}
});
var golem = require('golem');
// Usage
var foo = golem({f: 'foo'});
var bar = golem({b: 'bar'});
var qux = golem.compose(foo, bar); // or: golem().mixin(foo, bar);
var q = qux.create();
var b = bar.create();
b.b; // 'bar'
q.f; // 'foo'
q.b; // 'bar'
bar.isInstance(b); // true
bar.isInstance(q); // true
qux.isInstance(q); // true
qux.isInstance(b); // false
@pluma
Copy link
Author

pluma commented Oct 29, 2013

stampit-ish object factory definitions with real multiple inheritance.

This requires harmony proxies, so it only works with node --harmony. Requires harmony-reflect and uniq from NPM.

If you're familiar with stampit: initializer is the equivalent of enclose, extend is the equivalent of methods, defaults is the equivalent of state. Getters/setters work as expected and arrays, objects and null-prototype objects in the defaults are cloned on golem creation.

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