Introduction
In this tutorial, we’ll dive deep into the Factory Method Pattern, a foundational design pattern in the realm of software development, tailored specifically for TypeScript enthusiasts. The Factory Pattern is a creational design pattern that provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created. This pattern is particularly useful when there is a need to encapsulate the instantiation process of a product, making a system independent of how its products are created, composed, and represented.
Read about Strategy Pattern with Java Examples
What is the Factory Method Pattern?
The Factory Method Pattern defines an interface for creating an object but lets subclasses decide which class to instantiate. It lets a class defer instantiation to subclasses, ensuring that the use of a class is separated from its instantiation.
Why Use the Factory Method Pattern?
- Flexibility and Scalability: Easily introduce new types of products without disturbing the existing client code.
- Decoupling: The client code is decoupled from the concrete classes, relying instead on interfaces or abstract classes.
- Single Responsibility Principle: You can move the product creation code into one place in the application, making the code easier to support.
Real-World Analogy
Imagine you are an architect. You sketch a blueprint for a house (the product), but the actual building of houses is done by construction companies (concrete creators). Each construction company follows your blueprint but has the freedom to approach the construction with its unique techniques and materials.
Implementing the Factory Pattern in TypeScript
Scenario
In this scenario, our application needs to notify users about various events, such as new messages, system updates, or promotional offers. Users can choose their preferred notification method. Our goal is to design a flexible system that can easily accommodate new notification methods without modifying the existing codebase.
Step 1: Define the Product Interface
First, we define a Notification
interface that all concrete notifications will implement. This interface will have a send
method.
interface Notification {
send(message: string): void;
}
Step 2: Create Concrete Products
Next, we implement concrete products that represent different notification methods.
class EmailNotification implements Notification {
public send(message: string): void {
console.log(`Sending email: ${message}`);
}
}
class SMSNotification implements Notification {
public send(message: string): void {
console.log(`Sending SMS: ${message}`);
}
}
class PushNotification implements Notification {
public send(message: string): void {
console.log(`Sending push notification: ${message}`);
}
}
Step 3: Define the Creator Class
The creator class declares the factory method that is supposed to return an object of type Notification
. It also includes a notifyUser
method that uses the Notification
object to send a message.
abstract class NotificationCreator {
public abstract createNotification(): Notification;
public notifyUser(message: string): void {
const notification = this.createNotification();
notification.send(message);
}
}
Step 4: Implement Concrete Creators
We then implement concrete creators for each notification type. Each creator overrides the factory method to return an instance of its corresponding concrete product.
class EmailNotificationCreator extends NotificationCreator {
public createNotification(): Notification {
return new EmailNotification();
}
}
class SMSNotificationCreator extends NotificationCreator {
public createNotification(): Notification {
return new SMSNotification();
}
}
class PushNotificationCreator extends NotificationCreator {
public createNotification(): Notification {
return new PushNotification();
}
}
Step 5: Using the Factory Method
Finally, we demonstrate how to use the factory method in our application. This example simulates sending notifications through different methods based on the creator instance.
function clientCode(creator: NotificationCreator, message: string) {
creator.notifyUser(message);
}
console.log('User prefers email notifications:');
clientCode(new EmailNotificationCreator(), 'You have a new message!');
console.log('\nUser prefers SMS notifications:');
clientCode(new SMSNotificationCreator(), 'Your package has been delivered.');
console.log('\nUser prefers push notifications:');
clientCode(new PushNotificationCreator(), 'A new update is available for your app.');
Conclusion
This example illustrates how the Factory Method Pattern can be applied to create a flexible and scalable notification system in TypeScript. By encapsulating the creation logic within specific creator classes, our application can easily adapt to new notification methods without altering existing code. This pattern not only promotes loose coupling but also enhances the extensibility of the application.
Read more about Factory Pattern in Refactoring Guru.
Pingback: Understanding TypeScript Generics: Why and How to use them? - Just Code