What is a Factory?
To understand what a factory does, you should first know the problem that it tries to solve. Creating a new custom object directly requires a call to the constructor with the
new keyword and any required arguments. This offers complete control over creation: Powerful… but not always feasible.
Working with a complex constructor may be overkill in some simple cases, or you may find yourself passing the same arguments to the same constructors over and over and over again. Both situations are at best annoying, and at worst completely unscalable. No one likes copypasta in their code.
Lets introduce a factory to help clean up this mess. A factory’s job is to create objects so that you don’t have to, which can be advantageous for several reasons. If you are creating a public module to share on npm or even privately within your own team, adding a factory interface can make your module easier to work with. This is especially true if setup requires additional steps after calling the constructor. A factory can consolidate and standardize on all of this custom-creation for both you and your users, dictating exactly how new objects should be created.
Using a factory, the example above becomes:
If you find yourself struggling with complex object creation across multiple parts of your application, this pattern could be the answer. By centralizing this logic in a single place, you keep it from spreading across the rest of your code base where it shouldn’t belong.
Separate the Product
Before creating a factory, make sure that any custom types that you plan to use are already defined as separate modules. It can be tempting to keep both the factory and the product (the object returned by a factory) in the same file, but remember your training: small, single purpose modules. Separating the two modules will reduce confusion between the different responsibilities of each. In practice, testing coverage and code re-use are both improved as well.
The Factory Design Pattern
Both singleton and custom type patterns can be used as the base for your new factory. If you know that your entire application can share a single factory object, then building it as a singleton is almost always the right choice. A singleton can be easily required anywhere, and won’t need any complex construction or passing around of its own. But remember, your state is limited to the single module.
But sharing the same factory across your application won’t always cut it. Sometimes, a factory needs some set up and customization of its own. If this is the case, design your factory using the custom type pattern instead. This way your factory can expose some basic level of flexibility with each instance, while still encapsulating the product customization.
Once you are familiar with factories, you can begin to get creative with how you use them. The Abstract Factory pattern, for instance, offers even greater flexibility by taking the whole concept one step further. If you have several factories that each create different products from the same parent type, you can create a common interface between them. This lets you to swap between the factories themselves (depending on which type of object you’d like to create) without modifying the rest of your code.
Consider the Express application below:
Here you have two different factories for logged in and logged out users. They don’t share a common interface (
createAnonymousUser()) which means you will always need to check which kind of factory you’re working with before using it. This leads to lots of unnecessary
/*user is logged in */ checks throughout the application, when really all you need is one.
If you can consolidate each interface into something common across both, you will have implemented an abstract factory. Then, each factory can be used without worrying which type of user is being created. That decision is made once, and then carried on by the rest of your application.
A good custom type interface should be flexible enough to support all reasonable use cases. But this flexibility can come at a cost. As the number of supported options grows, the interface and required setup can also grow more complex. Worse, creation complexity can bleed out all over your application, with copy and pasted code blocks for each new object. Factories are a clean solution to return sanity to your application, by making complex and repeated customizations simpler and more maintainable.