Composing ‘Chainable’ Functions

Mattias Petter Johansson has a great video explaining the difference between composition and inheritance chains in development.

I won’t repeat the tutorial he gives in the video because frankly, his video is better than anything I could write. But!

I want to add a tweak to his factory function in order to make your factories “chainable”. Basically so you can call something like this:

[pastacode lang=”javascript” message=”” highlight=”” provider=”manual”]

sniffles.speed(15).walk();

[/pastacode]

If you aren’t familiar with factory functions, he also has a video here: https://www.youtube.com/watch?v=ImwrezYhw4w that again, explains things better than I could.

So here’s what we want to do. Let’s make a dog. A dog can walk and bark. Our dog will be our main “thing” factory:

[pastacode lang=”javascript” message=”” highlight=”” provider=”manual”]

// the thing it is
function Dog(name) {
  var state = {
    name: name
  };
  
  return Object.assign(
    {},
    barker(state),
    walker(state)
  );
}

[/pastacode]

The most perceptive of you might notice that we have something in here called a “barker” and a “walker”. They are functions that we will define in a minute. The thing to notice here is that this function takes a “name” parameter. Essentially, this follows the same end user workflow as a class. A developer using your code will just have to use:

[pastacode lang=”javascript” message=”” highlight=”” provider=”manual”]

// use this for factories
var dog = Dog('sniffles');

// instead of classes
var dog = new Dog('sniffles');

[/pastacode]

So you can see, it’s almost the same thing! In fact, in most cases you’ll never notice a difference.

Now for the fun part. Let’s make these “doing” functions that take the state argument. Then lets make their methods chainable.

A dog can bark, so let’s make a “barker” factory:

[pastacode lang=”javascript” message=”” highlight=”” provider=”manual”]

function barker(state) {
  var _state = state;
  return {
    bark: function() {
      console.log('I am a ' + _state.name);
    }
  };
}

[/pastacode]

Simple enough. However things get more complex when we want to make a “walker” factory. Instead of just having a function called “walk” we want to be able to set the speed of the walk, as well as have a default in case the user just calls .walk().

[pastacode lang=”javascript” message=”” highlight=”” provider=”manual”]

function walker(state) {
  var _state = Object.assign(
    {
      speed: 100
    },
    state
  );
  return {
    walk: function() {
      var speed = _state.speed || 50;
      console.log('walking at speed: ' + _state.speed);
    },
    speed: function(speed) {
      _state.speed = speed;
    }
  }
}

[/pastacode]

So now we can start walking!

[pastacode lang=”javascript” message=”” highlight=”” provider=”manual”]

// make a dog named sniffles
var sniffles = Dog('sniffles');

// set the walking speed
sniffles.speed(10);

sniffles.walk();

[/pastacode]

What would be nice is if you could update the speed as you go. If you haven’t made functionality to update any attribute of the state, there’s not really a good way to get changes in speed into sniffle’s state. Also, let’s say you want to limit a developer’s ability to tamper with the internal state of your object. To do this, it would be nice to set speed before we call walk. Of course, we could pass speed as an argument of walk:

[pastacode lang=”javascript” message=”” highlight=”” provider=”manual”]

sniffles.walk(1000);

[/pastacode]

But not we have to make special functionality inside our walk function to share the speed in case another function needs to use speed as well. Not good!

So by adding:

[pastacode lang=”javascript” message=”” highlight=”” provider=”manual”]

return this;

[/pastacode]

to the speed function we return the context of the speed function which happens to be sniffles! Now we can chain speed().

The function now looks like this:

[pastacode lang=”javascript” message=”” highlight=”” provider=”manual”]

function walker(state) {
  var _state = Object.assign(
    {
      timeToWalk: 100
    },
    state
  );
  return {
    walk: function() {
      var speed = _state.speed || 50;
      console.log('walking at speed: ' + _state.speed + ' for ' + _state.timeToWalk + ' seconds.');
    },
    speed: function(speed) {
      _state.speed = speed;
      return this;
    }
  }
}

[/pastacode]

So now we can do:

[pastacode lang=”javascript” message=”” highlight=”” provider=”manual”]

sniffles.speed(15).walk();

[/pastacode]

Here’s a codepen so you can play with it:

 

See the Pen Dog Factory (chainable) by Mike Newell (@newshorts) on CodePen.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.