What do “ design patterns” mean?
Design patterns are essential tools in a developer’s arsenal. They provide reusable solutions to common problems in software design. it’s like a blueprint or a template that you can adapt to solve specific problems in your code. Today, we’ll go through the Factory Method Pattern, a creational pattern that helps manage object creation in an efficient and flexible way.
In this article, we’ll break down the Factory Method Pattern, understand the problems it solves, and see how it’s used in real-world projects. By the end, you’ll have a solid understanding of how to implement it in TypeScript.
Table of Contents
Factory Method Definition
The Factory Method is a creational design pattern that defines an interface for creating objects but allows subclasses or specific implementations to decide which object to instantiate.
Instead of instantiating objects directly in your code (e.g., using the new keyword), the Factory Method delegates the object creation responsibility to a factory method.
Key Characteristics:
Encapsulation of Object Creation: Clients only know the interface, not the concrete implementation.
Flexibility: You can introduce new object types without modifying the existing codebase.
Scenario: Understanding the Problem
Imagine you’re working on a notification system for a messaging app. Your app needs to send notifications through different channels like Email, SMS, and Push Notifications.
At first, it’s tempting to create objects for these notification types directly wherever needed:
const emailNotification = new EmailNotification();
emailNotification.send("Welcome to our platform!");
const smsNotification = new SMSNotification();
smsNotification.send("Your order has been shipped!");
This approach works fine — until it doesn’t.
What Happens When the Requirements Change?
It becomes error-prone and time-consuming, especially as the number of notification types increases.
Complex object initialization, suppose certain notification types require additional configurations (e.g., API keys for SMS, custom headers for Push Notifications). Adding these configurations everywhere you create the objects leads to repetitive code and potential bugs.
Code duplication and tight coupling, your client code becomes tightly coupled with the concrete implementations (EmailNotification, SMSNotification, etc.), making it harder to change or test.
What Do We Want Instead?
We need a solution where:
Object creation is centralized and reusable: The code to initialize each notification type should live in one place.
New notification types can be added easily: Adding for example a WhatsAppNotification type or any future type should not require changes to the existing client code.
Client code is decoupled from implementations: The client should rely on an interface, not specific classes.
This is where the Factory Method Pattern shines. It addresses these challenges by encapsulating object creation and delegating the responsibility to a centralized factory while respecting the single responsibility principle.
Now, with this problem in mind, let’s see how the Factory Method Pattern solves it effectively.
Step 1: Define the Product Interface
interface Notification {
send(message: string): void;
}
Step 2: Implement Concrete Products
class EmailNotification implements Notification {
send(message: string): void {
console.log(`Email sent with message: ${message}`);
}
}
class SMSNotification implements Notification {
send(message: string): void {
console.log(`SMS sent with message: ${message}`);
}
}
class PushNotification implements Notification {
send(message: string): void {
console.log(`Push notification sent with message: ${message}`);
}
}
Step 3: Define the Creator Class
abstract class NotificationFactory {
abstract createNotification(): Notification;
sendNotification(message: string): void {
const notification = this.createNotification();
notification.send(message);
}
}
Step 4: Implement Concrete Factories
class EmailNotificationFactory extends NotificationFactory {
createNotification(): Notification {
return new EmailNotification();
}
}
class SMSNotificationFactory extends NotificationFactory {
createNotification(): Notification {
return new SMSNotification();
}
}
class PushNotificationFactory extends NotificationFactory {
createNotification(): Notification {
return new PushNotification();
}
}
Step 5: Client Code
const factories: NotificationFactory[] = [
new EmailNotificationFactory(),
new SMSNotificationFactory(),
new PushNotificationFactory(),
];
factories.forEach(factory => {
factory.sendNotification("Hello, World!");
});
Output:
Email sent with message: Hello, World!
SMS sent with message: Hello, World!
Push notification sent with message: Hello, World!
Real-Life Projects That Use the Factory Method
Game Development
In games, the Factory Method is often used to create enemies, weapons, or levels dynamically based on difficulty or player state.Frameworks:
Angular: Dependency Injection in Angular can be considered a real-world use of the Factory Method. Factories are used to provide instances of services dynamically.
React Context: Factories are sometimes used to manage state or create context providers dynamically.
Conclusion
The Factory Method is a simple yet powerful design pattern that can make your codebase more flexible, scalable, and easier to maintain.
In the next article, we’ll explore another creational pattern: Abstract Factory. Stay tuned, and stay curious !
Top comments (0)