Picture this. A car dealership is selling cars 🚗. Suddenly, they want to branch out and sell trucks 🚛. However, you had initially programmed the order and sale system to handle cars. What do you do now? Do you duplicate the majority of the business logic in the system to also handle trucks specifically?
Sure, it’s a somewhat quick win to do this. However, a short while later, the Dealership decides it’s going to start selling Motorbikes.
🤦 Oh no. More code duplication? What happens if the order system needs to change, do we need to update it now in three places!!?
We’ve all been there. It’s hard to predict when this situation might occur, but when it does, know that there is a solution that may initially require a bit of refactoring, but will surely mean a more maintainable solution, especially when the dealership says it’s going to start selling Boats! 🛥️
In this article, we will discuss:
- 💪 A solution - The factory pattern
- 🤔 When should I use it?
- 🤯 Some advantages and disadvantages
- ❓ Where is it being used in the frontend world?
- 🏭 Let’s see an example!
The factory pattern is a creational design pattern that adds an abstraction layer over common base behavior between multiple objects of a generic type.
The client code, the code that will use this layer, does not need to know the specifics of a behavior’s implementation, as long as it exists.
If we take our car dealership turned multi-vehicle dealership example above, we can see that the common ground between the cars, trucks, and boats is that they are all vehicles. The OrderSystem
within the dealership only needs to work with a base Vehicle
, it does not need to know the specifics about the Vehicle
being processed.
Let’s take a quick look at a UML diagram to illustrate this:
As we can see from the diagram, the system contains concrete implementations of the Vehicle
interface. The OrderSystem
doesn’t know or need to know what these concrete implementations are; it simply relies on the VehicleFactory
to create and return them when required, therefore decoupling our OrderSystem
from the Vehicles
that the dealership wants to sell! 🚀🚀🚀
Now, they can branch out to as many vehicles as they like, and we only ever have to create a new implementation of the Vehicle
interface and update our VehicleFactory
to create it! 🔥🔥🔥
There are a few situations, other than the one I described above, where this pattern fits perfectly:
Advantages:
Disadvantages:
Surprisingly (well maybe not), Angular allows the usage of factories in their Module Providers. Developers can provide dependencies to modules using a factory, which is extremely useful when information required for the provider is not available until runtime.
You can read more about them in the Angular Docs for Factory Providers.
A great example of this in the frontend is cross-platform UIs.
Imagine having a cross-platform app that shows a dialog box. The app itself should allow a Dialog
to be rendered and hidden. The Dialog
can render differently on a mobile app than it does on a desktop. The functionality, however, should be the same. The app can use a factory to create the correct Dialog
at runtime.
For this example, we are going to use TypeScript to create two implementations of a Dialog
, a MobileDialog
, and a DesktopDialog
. The app will use the User Agent String to determine if it is being viewed on a desktop or mobile device and if it will use the Factory
to create the correct Dialog
.
Note: Normally, it is more ideal to develop one responsive
Dialog
; however, this is an example to illustrate the factory pattern.
Let’s start by creating a base Dialog
interface:
interface Dialog {template: string;title: string;message: string;visible: boolean;hide(): void;render(title: string, message: string): string;}
This interface defines the common behaviour and state that any concrete implementation will adhere to.
Let’s create these concrete implementations:
class MobileDialog implements Dialog {title: string;message: string;visible = false;template = `<div class="mobile-dialog"><h2>${this.title};</h2><p class="dialog-content">${this.message}</p></div>`;hide() {this.visible = false;}render(title: string, message: string) {this.title = title;this.message = message;this.visible = true;return this.template;}}class DesktopDialog implements Dialog {title: string;message: string;visible = false;template = `<div class="desktop-dialog"><h1>${this.title};</h1><hr><p class="dialog-content">${this.message}</p></div>`;hide() {this.visible = false;}render(title: string, message: string) {this.title = title;this.message = message;this.visible = true;return this.template;}}
There are only slight variations in this functionality, and you may argue that an abstract class would be a better fit for this as the render
and hide
methods are the same. For the sake of this example, we will continue to use the interface.
Next, we want to create our DialogFactory
:
class DialogFactory {createDialog(type: 'mobile' | 'desktop'): Dialog {if (type === 'mobile') {return new MobileDialog();} else {return new DesktopDialog();}}}
Our factory takes a type
and will subsequently create the correct implementation of the Dialog
.
Finally, our App
needs to consume our factory:
class App {dialog: Dialog;factory = new DialogFactory();render() {this.dialog = this.factory.createDialog(isMobile() ? 'mobile' : 'desktop');if (this.dialog.visible) {this.dialog.render('Hello World', 'Message here');}}}
Looking at the code above, we can see that the app does not need to know about the concrete implementations, rather, that logic is the responsibility of the DialogFactory
.
Hopefully, this code example has helped to clarify the factory pattern and its potential usage in the frontend world.
Personally, I understand the concept and the advantages of this pattern, but I do not like the focus and reliance on inheritance that is required for its implementation.
Feel free to discuss any other examples or your own opinions of this pattern as I’m still undecided on it. If you have any questions, feel free to reach out to me on Twitter: @FerryColum.
Free Resources