Factory Function Pattern In-Depth
There are plenty great introductions to Factory Functions, but few describe the details of the pattern. If you don’t know what a Factory Function is, please watch Factory Functions in Javascript or read the transcription.
This article will describe the smaller patterns within the overall Factory Functions Pattern. All code snippets are written in ECMAScript 2015. As of January 2016 these code snippets will require Babel until Node.js has full ECMAScript 2015 support.
The Basics
A Factory Function is just a Function that creates something. It is usually an Object, but it can be anything, a String, an Array, or even another Function. In this article we will focus on Factory Functions as a replacement for ECMAScript 2015 Class.
Here is a simple Factory Function.
// greeter.js
export default () => {
return {
greet() {
console.log('Hello World!');
}
};
}
That just defines the Factory Function but does not create an instance of the object. This is done is another file, typically the main file.
// main.js
import Greeter from './greeter';const greeter = Greeter();greeter.greet(); // prints Hello World!
The convention for Factory Functions is to capitalize the name. This way you can think of it like a class but you don’t use new. This is very similar to Scala Case Classes.
Dependency Injection
Let’s say we want to greet to a file or to an API. We will refactor the previous example such that we are not greeting directly to console.log.
// greeter.js
export default (outputStream) => {
return {
greet() {
outputStream.send(‘Hello World!’);
}
};
}
Notice greeter does not need to know how outputStream is implemented.
// main.js
import Greeter from './greeter';
import ConsoleOutputStream from './console-output-stream';const consoleOutputStream = ConsoleOutputStream();
const greeter = Greeter(consoleOutputStream);greeter.greet(); // prints Hello World!
We inject output stream in the main file. This is Dependency Injection. What is console output stream? It’s just another Factory Function!
// console-output-stream.js
export default () => {
return {
send(line) {
console.log(line);
}
};
}
Unit Testing
Greeter no longer depends on any global references (i.e. console.log), which means we can now unit test it without any dirty tricks.
We want to assert that the output stream was called with “Hello World!”. This is easy to do when we are in full control of the dependencies.
In JavaScript, it is easy to mock objects with an object literal. This technique is use to mock the output stream and pass it into greeter.
Mocha and Chai are the only tools we need.
// test/greeter.js
import { expect } from 'chai';
import Greeter from '../greeter';describe('greeter', () => {
it('should send a greeting to output stream', () => {
const outputStream = {
send(line) {
expect(line).to.equal('Hello World!');
}
}; const greeter = Greeter(outputStream);
greeter.greet();
});
});
No mocking library required!
Encapsulation
Private data is required for Encapsulation. With ECMAScript 2015 classes, private data is possible, but awkward. Let’s extend our example and make greeter stateful. We will allow others to configure the greeting message, but we will keep the data private.
// greeter.js
export default (outputStream) => {
let _message = 'Hello World!'; return {
greet() {
outputStream.send(_message);
},
set message(message) {
_message = message;
},
get message() {
return _message;
}
};
}
That set/get syntax might look foreign to you, but it is actually just combining ECMAScript 5.1 getters/setters with ECMAScript 2015 Enhanced Object Literals.
We can then use message like a normal property.
// main.js
import Greeter from './greeter';
import ConsoleOutputStream from './console-output-stream';const consoleOutputStream = ConsoleOutputStream();
const greeter = Greeter(consoleOutputStream);greeter.message = 'Salutations Earth.';
greeter.greet(); // prints Salutations Earth.
It is impossible to access the _message variable from outside of the Factory Function. Data is kept private. Difference instances of greeter will have their own private copy of _message and will not conflict.
Composition
If we keep the objects created using Factory Functions small, we can use composition to create new objects from smaller components. Let’s add some additional functionality so we have something to compose. We will add a wave gesture.
// gesturer.js
export default (outputStream) => {
return {
wave() {
outputStream.send('*Waves hand*');
}
};
}
To create a waving greeter, we simply create the two smaller components, then use Object.assign to compose into a single object.
// main.js
import Greeter from './greeter';
import Gesturer from './gesturer';
import ConsoleOutputStream from './console-output-stream';const consoleOutputStream = ConsoleOutputStream();
const greeter = Greeter(consoleOutputStream);
const gesturer = Gesturer(consoleOutputStream);const wavingGreeter = Object.assign({}, greeter, gesturer);wavingGreeter.message = 'Salutations Earth.';
wavingGreeter.greet(); // prints Salutations Earth.
wavingGreeter.wave(); // prints *Waves hand*
The waving greeter shares the same state as the components it is made up from. We can set a new message on greeter and waving greeter will use it.
Object.assign assigns properties from left to right. In the example, it assigns greet to the empty object, then wave to an object that contains greet.
If there are conflicts, the right most object wins. Or in other words calling a conflicting method is the same as calling the method on the right most object in the list. You can prevent methods from conflicting by wrapping objects and renaming methods before composing them.
Calling Sibling Methods
In all our previous examples we immediately returned the objects we constructed. In more complex objects we might want a method to call another method on the same object. This is possible, but we need to get a reference to the self.
// head-scratcher.js
export default () => {
const self = {
scratch(location) {
console.log(`scratching ${location}`);
},
confused() {
self.scratch('head');
}
}; return self;
}
Bounded Method References
One of the downsides of ECMAScript 2015 Classes is methods depend on the this reference. Unfortunately if you want to use a class method in function libraries such as Ramda, you will end up having to bind this back to the class instance.
Let’s see what this looks like with ECMAScript 2015 Classes.
// offset.js
export default class Offset {
constructor(delta) {
this.delta = delta;
} add(value) {
return value + this.delta;
}
}// main.js
import Offset from './offset';const increment = new Offset(1);console.log([1, 2, 3]
.map(increment.add.bind(increment))); // prints [ 2, 3, 4 ]
Objects created using Factory Functions don’t have this problem, since there is no this reference. Let’s see that same example with Factory Functions.
// offset.js
export default (delta) => {
return {
add(value) {
return value + delta;
}
};
}// main.js
import Offset from './offset';const increment = Offset(1);console.log([1, 2, 3]
.map(increment.add)); // prints [ 2, 3, 4 ]
Notice how much shorter and simpler the code is.
Types
Factory Functions can create informally typed objects. We simply add a getter type property to the object we wish to type.
// duck.js
export default () => {
return {
get type() {
return 'duck';
},
speak() {
return 'quack';
}
};
}
In place of instanceOf, we just use the type property.
// main.js
import Duck from './duck';
import Dragon from './dragon';const animals = [Duck(), Dragon()];animals.forEach((animal) => {
switch(animal.type) {
case 'duck':
console.log('safe to pet');
break;
case 'dragon':
console.log('run away');
break;
default:
console.log('be careful');
}
}); // prints safe to pet, then run away
Self-Instantiating Dependencies Anti-Pattern
The final section will talk about Factory Function Anti-Patterns. The first one is related to Dependency Injection. Before we can understand the Anti-Pattern, we need to understand Dependency Injection in more depth. It actually has two parts, components and injection. Components are only allowed to depend on other components. Injection happens once during application startup.
In our case Function Factories create the components. The Function Factories are used during injection to create the application.
The Self-Instantiating Dependencies Anti-Pattern is when a component creates a dependency on its own outside of injection. Let’s see this in code. We will take our last component gesturer and have it create its own output stream.
// gesturer.js
import ConsoleOutputStream from './console-output-stream';export default () => {
const outputStream = ConsoleOutputStream(); return {
wave() {
outputStream.send('*Waves hand*');
}
};
}
If you run main, the code still works, so what is the problem? There are many problems. We are violating the Single Responsibility Principle by having gesturer knowing about how to create a output stream. Gesturer doesn’t care how output stream is implemented, yet we have a hard link to the specific implementation.
The problem is not very obvious when we have a simple example like this, but imagine output stream requires many other dependencies. Things get complicated fast if you don’t know who creates your components and don’t know when they are created.
Another problem arises when we attempt to unit test gesturer. How do you mock out the console output stream? Well, you just can’t with ECMAScript 2015 modules (but you can if you are using require and proxyquire).
The solution is to always create all components during the initialization of your application, and inject everything. This keeps everything unit testable and prevents the need to pass down dependencies many layers just for something else to create a component. If you have the situation where you cannot create a component during application initialization but still require many dependencies, then create a factory service. Initialize your factory service like any other component and spawn new objects with normal methods.
I hope this gives you a better appreciation of Factory Functions. This is a living document and will grow over time more Factory Function Patterns get discovered. If you think I missed anything with Factory Functions, please tweet me.