Abstract Factory Pattern in TypeScript — ✨ Creational Design Pattern #2
🙋🏻♀️ What is the Abstract Factory Pattern?
The Abstract Factory Pattern is a design approach that provides an interface for creating families of related objects without specifying their concrete classes.
The idea behind the Abstract Factory Pattern is to encapsulate a group of related objects that can be created in various ways, depending on the needs of the application. This allows the client code to remain unaware of the specific types of objects being created, and to work with them in a generic way.
The pattern defines an abstract factory interface
that declares a set of methods for creating the various objects in the family. Each concrete factory that implements the abstract factory interface
is responsible for creating a specific set of related objects.
How is it different from Factory Pattern?
We covered Factory Pattern in the last story(#1).
The main difference between these two patterns is the scope of the objects being created. The Factory Pattern(#1) creates objects of a single type, while the Abstract Factory Pattern creates families of related objects.
The Factory Pattern is used to create objects of a single type, and it provides a way to create objects without specifying the exact class of object that will be created. The factory method takes some input, and based on that input, it creates an object of the appropriate type. This allows for loose coupling between the client code and the objects being created.
On the other hand, the Abstract Factory Pattern is used to create families of related objects, and it provides an interface for creating those objects without specifying their concrete classes. This makes sure that the client code and the objects created remain loosely coupled.
✨ Simple TypeScript Example
Imagine that we are using two separate design systems called ‘Blue’ and ‘Red’ to meet different needs, and you are creating a sample user interface application to briefly test each design system.
+--------------------+--------------------+--------------------+
| Variant \ Family | Blue | Red |
+--------------------+--------------------+--------------------+
| Token | Blue Token | Red Token |
| Button | Blue Button | Red Button |
| Dialog | Blue Dialog | Red Dialog |
| Form | Blue Form | Red Form |
| ... | Blue ... | Red ... |
+--------------------+--------------------+--------------------+
Here is an example code of the Abstract Factory pattern in TypeScript:
// Abstract Factory Interface
interface DesignSystemFactory {
createToken(): Token;
createButton(): Button;
createDialog(): Dialog;
createForm(): Form;
// ...
}
class BlueDesignSystemFactory extends DesignSystemFactory {
createToken(): Token {
return new BlueToken();
}
createButton(): Button {
return new BlueButton();
}
createDialog(): Dialog {
return new BlueDialog();
}
createForm(): Form {
return new BlueForm();
}
// ...
}
class RedDesignSystemFactory extends DesignSystemFactory {
createToken(): Token {
return new RedToken();
}
createButton(): Button {
return new RedButton();
}
createDialog(): Dialog {
return new RedDialog();
}
createForm(): Form {
return new RedForm();
}
// ...
}
The two concrete factories Blue...Factory
and Red...Factory
create different families of related objects.
In the client code:
// generic render function
function render(factory: DesignSystemFactory) {
const token = factory.createToken(); // don't know if it's blue of red
const Button = factory.createButton();
const Form = factory.createForm();
const Dialog = factory.createDialog();
return (
<div style={{ backgroundColor: token.color.primary }}>
<Button />
<Form />
<Dialog />
</div>
)
}
// Using Blue...
render(new BlueDesignSystemFactory());
// Using Red...
render(new RedDesignSystemFactory());
Finally, the render
function is used to create the objects based on the type of factory passed to it.
🧑🏻💻 Use it or Avoid it
When to use it
The Abstract Factory Pattern is beneficial when an application has to generate multiple sets of interrelated objects, which are designed to work together and you want to enforce these objects to be used in a consistent way.
This is especially useful when you need to change the implementation of a family of objects at runtime without impacting the client code.
When to avoid it
If it’s not necessary to modify the implementation of objects during runtime, it may not be required to create an abstract factory interface, and instead, a concrete factory can be used directly.
If there are a number of objects to create but they are actually not related families of objects, they don’t need to be created together. Or, if you only need to create a single type of object and its creation requirements are not complex, consider the Factory Pattern instead.
In short, the Abstract Factory Pattern promotes loose coupling between the client code and the objects being created, allowing for easy modification of the object families at runtime.