Design Patterns: Elements of Reusable Object-Oriented Software is an influential book that describes around two dozen software programming patterns. Over the years much has been written about those patterns to make the confusing parts easier for generations of developers – additional books got published and endless discussions on things like the difference between “Abstract Factory” and “Factory Method” took place in various online forums. But really, they shouldn’t have been this complicated – the purpose of the most design patterns can be summarized in just 3 words:

“Hide implementation details.”

Or more specifically in 7 words:

“Hide implementation details behind a common interface.”

Granted, the authors of the Design Patterns book famously stated: “Program to an interface, not an implementation.”, which means about the same thing, but then they went on to explain in great length how to achieve that goal within the bounds of traditional object oriented languages employing classes, and confused a lot of people along the way.

When Design Patterns first got published, the year was 1994, a rather primitive time when functional programming languages that support first class functions were rarely used outside academia. These days, though, we have mainstream languages such as JavaScript and Python that can be used to trivially implement many of the design patterns described in the book with only a few lines of code.

Let’s go over five popular design patterns and see how easy it is to implement them in JavaScript, the lingua franca of the web:

Factory

Despite the lofty name, a factory function just returns an object. We do that all the time in JavaScript without giving it a second thought.

var userService = {
	create: function (firstName, lastName) {
		function getFullName() {
			return firstName + ' ' + lastName;
		}
		return {firstName, lastName, getFullName};
	}
}

var newUser = userService.create('Asuka', 'Langley');
console.log(newUser.getFullName());
// Prints Asuka Langley

Decorator

Another pattern with a fancy name, the decorator pattern is quite simple: modify an object while keeping its original interface the same. In JavaScript, we modify objects all the time, so I suppose we are all decorators.

// The user service object has been "decorated" with a shiny name property
userService.name = 'userService';

// You can also create a new "decorated" object (use _.cloneDeep for nested objects)
var anotherUserService = {...userService, name: 'anotherUserService'};

Command

The Command pattern can actually be quite useful when there is a need to transparently change the behavior of certain functions. Instead of calling a function directly, you call a command function, which in turn calls the original function for you. When there is an intermediate layer between the caller and callee, there are a myriad of possibilities – you can

  • queue function calls instead of calling them right away,
  • log function calls complete with arguments passed,
  • cache function results,
  • route calls to other functions, great for creating test doubles among other things.

Here is a generic command runner object that logs function calls before calling the actual functions. Just make sure to add a name property to the objects you pass to the runner, if it cannot automatically determine the objects’ names.

var fn = {
    log: [],
    run: function () {
	    // To preserve 'this', we need the parent object
	    var parentObj = arguments[0];
	    // If the second argument is empty, the parent object is called as is
	    var func = arguments[1] ? parentObj[arguments[1]] : parentObj;
	    // Get arguments to pass to the function
	    var argList = [].slice.call(arguments, 2);
	     // Log function calls
	    fn.log.push([parentObj.name, func.name, ...argList]);
	    // Call function
	    return func.apply(parentObj, argList);
	}
};

var user01 = fn.run(userService, 'create', 'Shinji', 'Ikari');
var user02 = fn.run(userService, 'create', 'Asuka', 'Langley');
console.log(fn.log);
/* Prints:
[
    [ 'userService', 'create', 'Shinji', 'Ikari' ],
    [ 'userService', 'create', 'Asuka', 'Langley' ]
]
*/

Strategy

In JavaScript, it’s common to pass a function as an argument to customize the behavior of another function. Here’s an example from Lodash, a popular utility library:

var _ = require('lodash');
var collection = {'a': 1, 'b': '2', 'c': 3};

console.log(_.pickBy(collection, _.isNumber));
// Prints { 'a': 1, 'c': 3 }

console.log(_.pickBy(collection, _.isString));
// Prints { b: '2' }

Chain of Responsibility

If you’ve used a popular web service framework like Express or Restify, you’ve used chain of responsibility. Each web service request needs to go through several handler functions (aka middleware) to handle things like logging, rate limiting, authentication, and authorization before processing the actual request. Any handler can break the chain and throw an error. The implementation is left as an exercise to the reader, but the process is quite simple:

  • Register handler functions.
  • Call them in order.
  • Check each handler function’s return value or the exception it has thrown to decide whether to call the next handler or exit.

Here is an example of the pattern in action from Restify:

// ...

var server = restify.createServer();

// Register global handler functions that are called for all routes
server.use(log.webRequest);
server.use(restify.plugins.throttle({burst: 100, rate: 50, ip: true}));
server.use(authentication.verify);

// Add custom handlers for each route below that are run after the global handlers

// Run an authorization handler and if all is well, return user profile data
server.get('/profile/:userId',
	authorization.canViewProfile,
    userController.getProfile
);

// Only admins can update admin settings
server.put('/admin/settings',
    authorization.isAdmin,
    adminController.updateSettings
);

// ...

David Wheeler famously said: “We can solve any problem by introducing an extra level of indirection.” Many a classic design pattern is nothing but an extra layer of abstraction. Classes aren’t usually the best way to implement those abstractions, but first class functions that can be passed around as arguments are.


Related: