Skip to content

Instantly share code, notes, and snippets.

@mrunderhill89
Created March 10, 2015 00:49
Show Gist options
  • Save mrunderhill89/9d5e1452815d2e2b3927 to your computer and use it in GitHub Desktop.
Save mrunderhill89/9d5e1452815d2e2b3927 to your computer and use it in GitHub Desktop.
Yet another implementation of an interface in Javascript.
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