I wanted to make simple JavaScript method, that returns a Point object with coordinates. Something like this:
But sometimes I had to use it as follows:
Here I use map with keys as number of arguments for each "overloaded" function. And if number of arguments passed in is satisfies on of those functions, than arguments apply to this overloaded function. Also see how first function calls itself recursively, so that if first argument is array with 3 items, than second overloaded function will be called eventually. Now I can easily add one more overloaded method:
But what if user use this function with number of arguments, that I don't support? It will die with some non-informative error. Let's add some checks:
Ok. Now it will throw Exception in all cases except those which it can handle. Also we can rid of those explicitly defined keys, which mean number of an argument of each overloaded function. Function object have property .length. It returns number of defined arguments. So let's use it:
But now code become much bigger than it was in first snippet. So, I decoupled business logic from all this wrapper stuff:
Logic from first overloaded function, which distribute array as arguments, also can be extracted into wrapper (with option for using it):
And finally I can improve it by rid of need to create array to pass my overloaded functions:
As you can see now you can put any function with different number of arguments into `overload` method and get nice overloaded method, that will handle all overloading logic. But! if you need to use overloading in case of optional arguments with default value, this method is not for you.
function point (x, y, z) { return {x:x, y:y, z:z}; }
But sometimes I had to use it as follows:
point.apply(null, xyz); //Where xyz == [x,y,z]But I hate to use `.apply` or `.call` methods too often, so I wanted to make overloaded method so it can handles 1 and 3 arguments. Most straight forward way may looks like this:
function point() { if(arguments.length==1){ return {x:arguments[0][0], y:arguments[0][1], z:arguments[0][2]}; }else if (arguments.length==3){ return {x:arguments[0], y:arguments[1], z:arguments[2]}; } }which I don't like at all. It's looks ugly. So I came with this thing:
function point() { return ({ 1: function(xyz) { return point.apply(null, xyz); }, 3: function (x, y, z) { return {x:x, y:y, z:z}; } }[arguments.length]).apply(null, arguments); }
Here I use map with keys as number of arguments for each "overloaded" function. And if number of arguments passed in is satisfies on of those functions, than arguments apply to this overloaded function. Also see how first function calls itself recursively, so that if first argument is array with 3 items, than second overloaded function will be called eventually. Now I can easily add one more overloaded method:
function point() { return ({ 1: function(xyz) { return point.apply(null, xyz); }, 2: function(x, y) { return {x:x, y:y}; }, 3: function (x, y, z) { return {x:x, y:y, z:z}; } }[arguments.length]).apply(null, arguments); }
But what if user use this function with number of arguments, that I don't support? It will die with some non-informative error. Let's add some checks:
function point() { function point() { var methods = { 1: function(xyz) { if(Array.isArray(xyz)){ return point.apply(null, xyz); }else{ throw "In one-argument case it should be an Array"; } }, 2: function(x, y) { return {x:x, y:y}; }, 3: function (x, y, z) { return {x:x, y:y, z:z}; } }, argLen = arguments.length; if(methods[argLen]){ return (methods[argLen]).apply(null, arguments); } throw "Unsupported number of arguments"; }
Ok. Now it will throw Exception in all cases except those which it can handle. Also we can rid of those explicitly defined keys, which mean number of an argument of each overloaded function. Function object have property .length. It returns number of defined arguments. So let's use it:
function point() { var methods = [ function(xyz) { if(Array.isArray(xyz)){ return point.apply(null, xyz); }else{ throw "In one-argument case it should be an Array"; } }, function(x, y) { return {x:x, y:y}; }, function (x, y, z) { return {x:x, y:y, z:z}; } ], argLen = arguments.length; for(var i in methods){ if(methods[i].length == argLen){ return methods[i].apply(null, arguments); } } throw "Unsupported number of arguments"; }
But now code become much bigger than it was in first snippet. So, I decoupled business logic from all this wrapper stuff:
var point = overload([ function(xyz) { if(Array.isArray(xyz)){ return point.apply(null, xyz); }else{ throw "In one-argument case it should be an Array"; } }, function(x, y) { return {x:x, y:y}; }, function (x, y, z) { return {x:x, y:y, z:z}; } ]); function overload(methods) { return function(){ var argLen = arguments.length; for(var i in methods){ if(methods[i].length == argLen){ return methods[i].apply(null, arguments); } } throw "Unsupported number of arguments"; } }
Logic from first overloaded function, which distribute array as arguments, also can be extracted into wrapper (with option for using it):
var point = overload([ function(x, y) { return {x:x, y:y}; }, function (x, y, z) { return {x:x, y:y, z:z}; } ], true); function overload(methods, distrFirstArray) { return function(){ var args = arguments; if(distrFirstArray && args.length == 1){ if(Array.isArray(args[0])){ args = args[0]; } else { throw "In one-argument case it should be an Array"; } } for(var i in methods){ if(methods[i].length == args.length){ return methods[i].apply(null, args); } } throw "Unsupported number of arguments"; } }
And finally I can improve it by rid of need to create array to pass my overloaded functions:
var point = overload( function(x, y) { return {x:x, y:y}; }, function (x, y, z) { return {x:x, y:y, z:z}; }, true ); function overload() { var methods = Array.prototype.slice.call(arguments), distrFirstArray = typeof methods[methods.length - 1] == "boolean"?methods.pop():false; return function(){ var args = arguments; if(distrFirstArray && args.length == 1){ if(Array.isArray(args[0])){ args = args[0]; } else { throw "In one-argument case it should be an Array"; } } for(var i in methods){ if(methods[i].length == args.length){ return methods[i].apply(null, args); } } throw "Unsupported number of arguments"; } }
As you can see now you can put any function with different number of arguments into `overload` method and get nice overloaded method, that will handle all overloading logic. But! if you need to use overloading in case of optional arguments with default value, this method is not for you.
Комментариев нет:
Отправить комментарий