Created
March 10, 2015 00:49
-
-
Save mrunderhill89/9d5e1452815d2e2b3927 to your computer and use it in GitHub Desktop.
Yet another implementation of an interface in Javascript.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define(['underscore'], function(_){ | |
var Interface = function () | |
{ | |
this.initialize.apply(this,arguments); | |
} | |
var default_methods = { | |
/* | |
Throws an exception if this object doesn't fit the given interface. | |
Otherwise, returns the object itself, which (unlike is_a) lets you chain | |
multiple checks together. | |
*/ | |
implements: function(interface){ | |
if (interface instanceof Interface){ | |
var missing = interface.missing(this); | |
if (_.isString(missing)){ | |
throw ("Interface: prototype does not have required method '"+missing+"'.\n"+ | |
"Prototype:"+_.keys(this.prototype)+"\n"+ | |
"Static:"+_.keys(this)); | |
} | |
}; | |
return this; | |
}, | |
// Simple boolean check to see if an object fits the interface. | |
is_a: function(interface){ | |
if (!(interface instanceof Interface)) return true; | |
return !_.isString(interface.missing(this)); | |
} | |
}; | |
function check_function(fun){ | |
if (_.isFunction(fun)){ | |
return fun; | |
}; | |
return undefined; | |
}; | |
function process_field(field, name){ | |
if (_.isObject(field)){ | |
if (_.isFunction(field.clone)){ | |
//Cloneable | |
return field.clone(); | |
} else if (_.isFunction(field.constructor)){ | |
//constructor/args pairing | |
return field.constructor.apply({}, field.args || []); | |
} else if (_.isFunction(field)){ | |
//Simple Function | |
return field(); | |
}; | |
}; | |
return field; | |
}; | |
function gather(target, value, key){ | |
target[key]=value; | |
}; | |
_.extend(Interface.prototype, { | |
initialize: function(params){ | |
params = _.defaults((params || {}), | |
{ | |
methods:[], | |
static:[] | |
} | |
); | |
this.methods = params.methods; | |
this.static = params.static; | |
}, | |
//Creates a new interface combining the terms | |
//from this one with any given terms. | |
extend: function(params){ | |
params = _.defaults((params || {}), | |
{ | |
methods:[], | |
static:[] | |
} | |
); | |
return new Interface({ | |
methods: this.methods.concat(params.methods), | |
static: this.static.concat(params.static), | |
}); | |
}, | |
/* | |
Creates a new class from given parameters, and automatically checks for interface compliance. | |
-constructor: constructor function. If not provided, creates a default using initialize. | |
-initialize: initialization function. | |
-methods: any functions included here are added to the function's prototype. | |
(i.e. they are available to any new instances) | |
-fields: | |
-static: any functions or fields included here are attached directly to the function. | |
(i.e. they behave like static or class functions in other languages) | |
-mixins: works with the Mixin class to attach mixed-in functions before checking against the interface. | |
Object structure goes like this: {property_name : mixin_function} | |
I know it should be the other way around, but Javascript doesn't like using non-strings for object keys. | |
*/ | |
implement: function(params){ | |
params = params || {}; | |
var fields = params.fields; | |
var init = check_function(params.initialize) || function(obj_params){ | |
_.reduce(_.mapObject(_.pick(obj_params, _.keys(fields)), process_field), gather, this); | |
}; | |
var cons = check_function(params.construct) || function(obj_params){ | |
var processed = _.mapObject(fields, process_field); | |
_.reduce(processed, gather, this); | |
init.apply(this, obj_params); | |
return this; | |
}; | |
// Add static properties to the constructor function, if supplied. | |
_.extend(cons, params.static, default_methods, | |
{prototype: _.extend(cons.prototype, params.methods, default_methods)} | |
); | |
return cons.implements(this); | |
}, | |
// Checks the target against the interface for missing functions. | |
// Returns the name of any missing functions when found. | |
// Otherwise, if the object checks out, returns false. | |
missing: function(imp){ | |
if (!imp.prototype){throw("Function missing its prototype:"+_.keys(imp))}; | |
var key; | |
for (var i in this.methods){ | |
key = this.methods[i]; | |
if (_.isUndefined(imp.prototype[key])){ | |
return key; | |
} | |
}; | |
for (var i in this.static){ | |
key = this.static[i]; | |
if (_.isUndefined(imp[key]) || !_.isFunction(imp[key])){ | |
return key; | |
} | |
}; | |
return false; | |
}, | |
}); | |
return Interface; | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment