Wednesday, December 24, 2014

Decoupling Dependencies in JavaScript

Dependency Injection

The goal of dependency injection is to decouple objects from their dependencies as much as possible. This means to decouple how the dependencies are selected, but not how they are used.

How a dependency is used depends on abstractions: the client code expects a particular interface and does not care what the dependency object is as long as it provides the interface. This works particularly well in JavaScript where duck-typing is favored over class inheritance.

The solution for decoupling the selection is to move the responsibility of selecting the actual dependency to another piece of code. This decoupling of is the implementation of a principle that Martin Fowler calls "Inversion of Control," and Robert C. Martin titles "Dependency Inversion," the fifth of his SOLID principles of object-oriented development (Fowler, Martin). They have more to say about that than I do! What I will discuss here are the forms that decoupling can take in JavaScript.

JavaScript Mixins

Building a JavaScript Mixin

In JavaScript a "mixin" is to add the properties of one or more source objects to a target object. To be precise, there are actually two kinds of mixins: a shallow copy and a deep copy. A shallow copy simply copies the references from the source to the target, and a deep copy clones the references.

So why a mixin? Well, there are two really good examples and both of them revolve around prototypes. The first involves AJAX. When JSON data is returned from a web service call it only contains data, no methods. That is intentional; if instantiating a JSON object added methods to the local program that would be an opportunity for evil code to be injected into a program from a remote source. But what if the data returned really represents something like a bank account, where our design depends on methods defined as part of the object? The ECMAScript 5 (current) and earlier specifications do not allow a prototype to be assigned to an existing object (our JSON object), nor does the JSON.parse method provide a means for a prototype to be attached. A prototype can only be attached to a new object during creation. So our solution is to create a new, empty object with the right prototype and then mixin the data properties from the JSON object.