22 January 2015

Command Pattern Deconstructed

I was rewriting the Underscore.js library in CoffeeScript when I came accross the invoke function. It reminded me of the execution method from the command pattern in Addy Osmani's book Learning JavaScript Design Patterns:

carManager.execute = function ( name ) {
    return carManager[name] && carManager[name].apply( carManager, [].slice.call(arguments, 1) );
};

This method takes an argument called name which holds a string. The string represents the method name we want to call on the carManager object.

&& Operator Short Circuiting

carManager[name] && ... means 'if the method we want to call on carManager exist then evaluate the right hand side of the expression and return its value'. It is worth noting here that when the && operator is used, if every part of the expression evaluates to true then the value of the last expression evaluated will be returned. For example:

'hey' && 'lol' && 32

Will return 32, since it was the last expression to be evaluated. Likewise, if you have any expressions that evaluate to false, then the first expression which evaluates to false in the chain of operations will be returned.

undefined && false && 44

Will return undefined. However, if we switched the order of false and undefined then false would be returned instead. If carManager[name] does not exist then undefined will be returned, otherwise the second part of expression will be executed:

Using apply to Invoke Methods

carManager[name].apply( carManager, [].slice.call(arguments, 1) );

is essentially the same thing as

carManager[name](arguments.slice(1));

[].slice is initializing an empty array instance and using it to gain access to the slice method. We when use call on the slice method and pass in (arguments, 1), which simply returns a new array of arguments including all but the first. In this case we are only excluding the first argument because the first argument is the name variable which we are already have access to.

Writing Specs for _.invoke

The following Jasmine specs describe the expected behavior for _.invoke:

describe "invoke", ->

  it "should call sort method on each element in an array and return results in an array", ->
    result = _.invoke([[5, 1, 7], [3, 2, 1]], "sort");
    expect(result).toEqual([[1, 5, 7], [1, 2, 3]])

  it "should call sort method on each value in an object and return results in an array", ->
    result = _.invoke({a: [5, 1, 7], b: [3, 2, 66]}, "sort")
    expect(result).toEqual([[1, 5, 7], [2, 3, 66]])

  it "should pass extra arguments onto method invocation", ->
    result = _.invoke(["lol"], "concat", "bbq")
    expect(result).toEqual(["lolbbq"])

_.invoke accepts a container, a method name, and any amount of extra arguments. This method then calls the method which corresponds to the method name on each item in the container and returns an array of the results. Any additional arguments passed into _.invoke will be applied to the invocation of each item in the container. We can write this entire method by modeling the pattern deconstructed above:

 _.invoke = (container, methodName) ->
    if Array.isArray(container)
      for element in container
        element[methodName].apply(element, [].slice.call(arguments, 2))
    else
      for key of container
        container[key][methodName].apply(container[key], [].slice.call(arguments, 2))