diffusion
Version:
Diffusion JavaScript client
170 lines (147 loc) • 4.83 kB
JavaScript
var functions = require('util/function');
var array = require('util/array');
/**
* @module util/interface
*/
/**
* Create an interface matcher, bound to a named set of required members. The
* returned matcher should be used with the {@link implements} function.
* <p>
* This facilitates creating a well-defined API that may be documented
* independant of the actual implementation.
* <P>
* Interfaces may specify one or more additional interfaces to inherit from.
*
* @example
* var interface1 = interface('Interface1', [
* // A method that does something
* 'interface1Method'
* ]);
* var interface2 = interface('Interface2', [
* // A method that does something else
* 'interface2Method'
* ]);
*
* @example
* var interface3 = interface('Interface3, interface1, interface2, [
* // Yet another method
* 'interface3Method'
* ]);
*
* @param {String} name - The interface name
* @param {Function} [interfaces] - Interfaces this extends
* @param {String[]} members - The set of interface members
* @returns {Function} The interface matcher
*/
function _interface() {
var args = array.argumentsToArray(arguments);
var name = args.shift();
var members = args.pop() || [];
args.forEach(function(ex) {
members = members.concat(ex.members);
});
var boundMembers = members.map(function(member) {
return {
name : member,
check : function(impl) {
return impl[member] === undefined;
},
throw : function() {
throw new Error(name + '#' + member + '" must be implemented');
}
};
});
var filter = function(implementation) {
return boundMembers.filter(function(member) {
return member.check(implementation);
});
};
filter.toString = function() {
return name;
};
filter.members = members;
return filter;
}
/**
* Create a constructor function from a given implementation and 1 or more
* interfaces. Objects created by the constructor will be guaranteed to provide
* the methods/properties defined by the specified interfaces.
* <p>
* The implementation argument may define a <code>__constructor</code> property.
* This will always be called when an object is created. There is no guarantee
* that this will be returned as the constructor function. The returned
* constructor is guaranteed to maintain type identity via <code>instanceof</code>.
* <p>
* <pre><code>
* var constructor = _implements(interface1, interface2, {
* __constructor : function MyClass(arg1, arg2) {
* this.arg2 = arg2;
*
* this._interface1Method = function() {
* return arg1;
* };
* },
* interface2Method : function() {
* return this.arg2;
* }
* });
*
* var instance = new constructor("Hello", "world");
*
* instance._interface1Method(); // => "Hello";
* instance._interface2Method(); // => "world";
* </code></pre>
* @param {Function} interfaces - The interface matchers
* @param {Object} implementation - The implementation
* @returns {Function} A constructor for the given implementation
*/
function _implements() {
var args = Array.prototype.slice.call(arguments, 0);
var impl = args.pop();
var unsatisfied = [];
var constructor;
if (impl instanceof Function) {
constructor = impl;
impl = constructor.prototype;
} else {
// eslint-disable-next-line no-underscore-dangle
constructor = impl.__constructor || function() { };
for (var m in constructor.prototype) {
impl[m] = constructor.prototype[m];
}
}
// The joys of duck type. Quack quack
args.forEach(function(_interface) {
unsatisfied = unsatisfied.concat(_interface(impl));
});
constructor.prototype = impl;
if (unsatisfied.length === 0) {
return constructor;
} else {
var proxy = function() {
var instance = functions.newWithArguments(constructor, arguments);
unsatisfied.forEach(function(m) {
if (m.check(instance)) {
m.throw();
}
});
return instance;
};
// Spoof string-coercion
proxy.toString = function() {
return constructor.toString();
};
// Allow proper type introspection
proxy.isPrototypeOf = function(c) {
return c instanceof constructor;
};
// Preserve reference to the original constructor
// eslint-disable-next-line no-underscore-dangle
proxy.__constructor = constructor;
return proxy;
}
}
module.exports = {
_interface : _interface,
_implements : _implements
};