UNPKG

diffusion

Version:

Diffusion JavaScript client

170 lines (147 loc) 4.83 kB
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 };