Factory Design Pattern
Table of contents
Factory Design Pattern is a creational design pattern which means object will be created at compile time.
It is categories into three design patterns.
Simple Factory
Factory Method Pattern
Abstract Factory Pattern
Let’s Understand Each of them one by one.
Simple Factory
Here we simple move the instantiation logic to a separate class and most commonly to a static method of this class.
Some do not consider simple factory as a “design pattern”, as its simply a method that encapsulates object instantiation.
Usage
- Typically we want to use simple factory, if we have more than one option when instantiating object and a simple logic is used to choose correct class.
UML Diagram of simple factory pattern
Code Implementation
interface Product {
name: string;
getName(): string;
}
class Product1 implements Product {
name: string;
constructor() {
this.name = "Product1";
}
getName(): string {
return this.name;
}
}
class Product2 implements Product {
name: string;
constructor() {
this.name = "Product2";
}
getName(): string {
return this.name;
}
}
/*
Code Accessible to Client
*/
class SimpleFactory {
static getProduct(productName: string): Product {
switch (productName) {
case 'Product1':
return new Product1();
case 'Product2':
return new Product2();
default:
throw new Error('Invalid Product name');
}
}
}
/*
Client Code which will access the simpleFactory
Product1 and Product2 have same type Product.
*/
const product1 = SimpleFactory.getProduct('Product1');
console.log(product1.getName());
const product2 = SimpleFactory.getProduct('Product2');
console.log(product2.getName());
Implementation Considerations
Simple factory can be just a method in existing class. Adding a separate class however allows other parts of your code to use simple factory more easily.
Simple factory doesn’t need any state tracking. So it’s best to keep this as a static method.
Real World Example of Simple Factory
The java.txt.NumberFormat class has getInstance method, which is an example of simple factory.
Pitfalls of Simple factory
The criteria used by simple factory to decide which object to instantiate can get more complex over time. If you find yourself in such situation then use Factory Method Pattern.
Factory Method Pattern
We want to move the object creation logic from our code to separate class.
We let subclasses decide which object to instantiate by overriding the factory method (will discuss this defination later in detail).
Usage
We use this pattern when we do not know in advance which class we may need to instantiate beforehand and also to allow new classes to be added to system and handle their creation without affecting client code.
UML of Factory Method
Products are created by their factory method.
As mentioned above, here we let subclasses (CreatorA, creatorB) decide which class (ProductA or ProductB) to instantiate by overriding the factory Method (Overriding the factory method which is in Creator)
Note: Creator and Product can also be abstract class, which will get more clear by code implementation example.
Use Case of Factory Method Pattern
Suppose we have a system which requires messages of different formats like (text, message, json etc…) and in future there may be requirement of more new formats.
So, implement simple factory to get different message format objects will not be an ideal choice, because it will result in voilation of open close principle as we are changing the switch case ladder again and again in the SimpleFactory class.
So, it’s better to create different MessageFactory for different type of message formats.
Example UML Diagram
Code Implementation
abstract class Message {
abstract getContent(): unknown;
addDefaultHeaders() {
console.log('Add default headers');
}
encrypt() {
console.log('Encrypt the header');
}
}
class JSONMessage extends Message {
getContent() {
return {
"key": "value"
};
}
}
class TextMessage extends Message {
getContent() {
return "text";
}
}
abstract class MessageCreator {
getMessage(): Message {
const msg: Message = this.createMessage();
msg.addDefaultHeaders();
msg.encrypt();
return msg;
}
abstract createMessage(): Message;
}
class JSONMessageCreator extends MessageCreator {
createMessage(): Message {
return new JSONMessage();
}
}
class TextMessageCreator extends MessageCreator {
createMessage(): Message {
return new TextMessage();
}
}
/* client code */
// Generic function to print message
function printMessage(creator: MessageCreator) {
const message = creator.getMessage();
console.log("Message Content:", message.getContent());
}
const jsonCreator = new JSONMessageCreator();
printMessage(jsonCreator);
const textCreator = new TextMessageCreator();
printMessage(textCreator);
Implementation Considerations
The creator can be a concrete class and provide a default implmentation for the factory method. In such cases you will create some “default” object in creator class;
You can also use the simple factory way of accepting additonal arguments to choose between the different object types. Subclasses can then override factory method to selectively create different objects for same criteria.
Note: Remember, the most defining characteristic of factory method pattern is “subclasses providing the actual instance”. So, static methods returning object instances are technically not Gang of Four factory methods.
Real World Example of Factory Method pattern
- The java.util.collection (or java.util.AbstractCollection) has an abstract method called iterator(). This method is an example of factory method.
Pitfalls of Simple factory
Complex to implement. More classes involved and need more unit testing.
Have to start with factory method pattern from the beginning and it’s not easy to refactor existing code into factory method pattern.
Simple Factory vs Factory Method pattern
Simple Factory pattern | Factory Method Pattern |
We simply move our instantiation logic away from client code. | Factory method is more useful when you want to delegate object creation to subclasses. |
Simple factory know about all clients whose object it can create. | In factory method we don’t follow in advance about all product subclasses. |
Abstract Factory
The Abstract Factory pattern is the way of Organizing how you create groups of things that are related to each other.
It provides a set of rules or instructions that let you create different types of things without knowing exactly what those things are. This helps you keep everything organized and lets you switch between different types easily, following the same set of rules.
Abstract Factory makes use of factory Method pattern. You can think of abstract factory as an object with multiple factory methods.
Usage
- It is used when we have two or more objects which worked together forming a set and there can be multiple sets than can be created by client code.
UML of Abstract Factory Pattern
Use Case of Abstract Factory Pattern
Suppose you are infrastructure as a service, which provides different infrastructure service like AWS, Azure, Google Cloud Platform on your platform without changing the funtionality.
Your Platform provides Instance Module and provies a way to select which Module to use (AWS, Azure, GCP) .
Example UML Diagram
Code Implementation
// Abstract Factory Interface
interface ResourceFactory {
createInstance(memory: string, compute: string): Instance;
createStorage(size: string): Volume;
}
// Concrete AWS Factory
class AWSResourceFactory implements ResourceFactory {
createInstance(memory: string, compute: string): Instance {
return new EC2Instance(memory, compute);
}
createStorage(size: string): Volume {
return new AwsVolume(size);
}
}
// Concrete Azure Factory
class AzureResourceFactory implements ResourceFactory {
createInstance(memory: string, compute: string): Instance {
return new VirtualMachine(memory, compute);
}
createStorage(size: string): Volume {
return new AzureVolume(size);
}
}
// Abstract Product - Instance
abstract class Instance {
constructor(protected memory: string, protected compute: string) {}
abstract start(): void;
abstract stop(): void;
abstract attachStorage(storage: Volume): void;
}
// Concrete Product - EC2Instance (AWS)
class EC2Instance extends Instance {
start() { console.log("AWS EC2 Instance started"); }
stop() { console.log("AWS EC2 Instance stopped"); }
attachStorage(storage: Volume) {
console.log(`AWS EC2 attached storage with ID: ${storage.getId()} of size ${storage.size}`);
}
}
// Concrete Product - VirtualMachine (Azure)
class VirtualMachine extends Instance {
start() { console.log("Azure Virtual Machine started"); }
stop() { console.log("Azure Virtual Machine stopped"); }
attachStorage(storage: Volume) {
console.log(`Azure Virtual Machine attached storage with ID: ${storage.getId()} of size ${storage.size}`);
}
}
// Abstract Product - Volume
abstract class Volume {
constructor(public size: string) {}
abstract getId(): string;
}
// Concrete Product - AwsVolume (AWS)
class AwsVolume extends Volume {
private static idCounter = 1;
private id: string;
constructor(size: string) {
super(size);
this.id = `AWS-VOL-${AwsVolume.idCounter++}`;
}
getId(): string {
return this.id;
}
}
// Concrete Product - AzureVolume (Azure)
class AzureVolume extends Volume {
private static idCounter = 1;
private id: string;
constructor(size: string) {
super(size);
this.id = `AZURE-VOL-${AzureVolume.idCounter++}`;
}
getId(): string {
return this.id;
}
}
/* Client Code */
// Generic Function to Abstract Client Code
function provisionResources(factory: ResourceFactory, memory: string, compute: string, size: string) {
const instance = factory.createInstance(memory, compute);
const storage = factory.createStorage(size);
instance.attachStorage(storage);
instance.start();
return { instance, storage };
}
console.log("Provisioning AWS Resources...");
const awsResources = provisionResources(new AWSResourceFactory(), "8GB", "4vCPU", "500GB");
console.log("\nProvisioning Azure Resources...");
const azureResources = provisionResources(new AzureResourceFactory(), "16GB", "8vCPU", "1TB");
Implementation Considerations
Factories can be implemented as singletons , we typically ever need one instance of its anyway. But make sure to familarize yourself with drawbacks of singletons.
Adding a new Product type requires changes to the base factory as well as all implementations of factory.
We provide the client code with concerte factory so that it can create objects.
Real World Example of Abstract Factory Pattern
- javax.xml.parsers.DocuementBuilderFactory. (This doesn’t 100% match with abstract factory of Gang of four design patterns as the class has static nowInstace() method which return actual factory class object).
Pitfalls of Abstract Factory
Tight Coupling: Suppose if we needs to add new products or make changes to existing ones, than we need to modify both the abstract factory interface and the concrete factory implementations. This can lead to tight coupling between the factories and their products, potentially violating the Open-Closed Principle.
Increased Complexity: We need to define interfaces for factories and their product families, create concrete factory implementations, and manage the relationships between factories and products.
Code Duplication: While implementing concrete factories patterns for different product families, we end up with duplicate code for creating similar types of objects across multiple factories.
Reduced Runtime Flexibility: Once we choose a concrete factory to create objects, it is typically challenging to switch to a different factory at runtime. This limitation can restrict your ability to adapt to changing requirements dynamically.
Limited Use Cases: Abstract Factory pattern is most valuable in scenarios where there are multiple related product families with interchangeable components. In simpler applications, the overhead of implementing and maintaining factories may not be justified.
Factory Method vs Abstract Factory Pattern
Abstract Factory | Factory Method Pattern |
Hides the factories as well as concrete objects from the client code. | HIdes the concrete objects which are used from the client code. |
Suitable when multiple objects are designed to work together and client use products from single family at a time. | Concerned with one product and its subclasses. Collaboration of product itself with other object is irrelevant. |