Picture this. A Car Dealership selling Cars 🚗. Suddenly, they want to branch out and sell Trucks 🚛. 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. A short while later, the Dealership decides it's gonna 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 initially might require a bit of refactoring but will surely mean a more maintainable solution, especially when the Dealership says it's gonna 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!
💪 A Solution - The Factory Pattern
The Factory Pattern is a creational design pattern that adds an abstraction layer over common base behaviour 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 the implementation of the behaviour, 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 are that they are all Vehicles. The Order System within the Dealership only needs to work with a base Vehicle, it doesn't 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
the Dealership wants to sell! 🚀🚀🚀
They can branch out to as many Vehicles as they like now and we only ever have to create a new implementation of the Vehicle
interface and update our VehicleFactory
to create it! 🔥🔥🔥
🤔 When should I use it?
There are a few situations, other than the one describe above where this pattern fits perfectly:
- Any situation where at or during runtime you do not know the exact type or dependency a specific portion of your code needs to work with.
- If you are developing a library, using the Factory Pattern allows you to provide a method for consuming developers to extend its internal components without requiring access to the source itself!
- If you need to save system resources, you can use this Pattern to create an Object Pool, where new objects are stored when they do not already exist, and will be retrieved from when they do exist, instead of creating a new one.
🤯 Some Advantages and Disadvantages
Advantages:
- It avoids tight coupling between the Consumer of the Factory and the Concrete Implementations.
- In a way it meets the Single Responsibility Principle by allowing the creation code to be maintained in one area.
- It also meets the Open/Closed Principle by allowing new Concrete Implementations to be added without breaking the existing code.
Disadvantages:
- It can increase the complexity and maintainability of the codebase as it requires a lot of new subclasses for each Factory and Concrete Implementation
❓ Where is it being used in the Frontend World?
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 on the Angular Docs for Factory Providers.
🏭 Let's see an example!
A great example for 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 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 the app is being viewed on a desktop or mobile device and 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 Implemenation 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 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 Factory:
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 it's 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 it requires for it's 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 ask below or reach out to me on Twitter: @FerryColum.
Top comments (9)
A project I recently worked on a project that turned out to really need this kind of structure, but I only realized this when it was a bit too late.
A very informational read, especially the 'when should I use this' part. :)
Thank you dearly
You're more than welcome!
Very helpful breakdown and I feel like I’ve come across it in early lessons of OO JS, but especially as applied to front end components, so much easier to understand, thanks
You're welcome!
Good post and all, but could you show us how to create a factory which doesn’t violate the open/closed principle? (I.e. a factory which when adding a third let’s say “PrintDialogue”, or whatever, a factory which doesn’t require modifying the factory’s code just to add another implementation) Maybe something that uses metadata from the children to determine what to do for instance? Thnx.
There's a more advanced form of the Factory Pattern which makes it easier to stay aligned with the Open Closed Principle, called the Abstract Factory Pattern: en.wikipedia.org/wiki/Abstract_fac...
However, in the implementation above, while the factory itself violates the open/closed principle, the
Dialog
itself doesn't and the factory aids you in adhering to it.You implement the Dialog, thus
extending
it, rather thanmodifying
it and you use the abstract to interface with the concrete implementation returned from the Factory.Thank you, This is what I was looking for.
Thanks for explanation and the good examples :-)
As you said, you don't like the fact that it relies on inheritance: wouldn't it be possible to create a factory that uses composition instead?
I'm not entirely sure. I mean, you could essentially have a bunch of behaviour classes that you piece together within the factory and return that new object, but I feel like that borders a different pattern, The Builder Pattern, potentially.