Let’s delve into both the Factory and Singleton patterns with specific examples to clarify how they can be implemented in Node.js.
Factory Pattern Example
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 you have a set of related classes and you want to instantiate one of them based on some input.
Here’s a simple example demonstrating the Factory pattern for creating different types of notifications (e.g., email, SMS):
// NotificationFactory.js
class Notification {
send() {
throw new Error("Method 'send()' must be implemented.");
}
}
class EmailNotification extends Notification {
send(message) {
console.log(Sending email with message: ${message}
);
// Logic to send email
}
}
class SMSNotification extends Notification {
send(message) {
console.log(Sending SMS with message: ${message}
);
// Logic to send SMS
}
}
class NotificationFactory {
static createNotification(type) {
switch (type) {
case "email":
return new EmailNotification();
case "sms":
return new SMSNotification();
default:
throw new Error("Notification type not supported.");
}
}
}
// Usage
const emailNotification = NotificationFactory.createNotification("email");
emailNotification.send("Hello, this is an email notification!");
const smsNotification = NotificationFactory.createNotification("sms");
smsNotification.send("Hello, this is an SMS notification!");
In this example, the NotificationFactory
class decides which type of notification to instantiate based on the input. This encapsulates the instantiation logic and separates it from the client code.
Singleton Pattern Example
The Singleton pattern ensures a class has only one instance and provides a global point of access to that instance. It’s useful when exactly one object is needed to coordinate actions across the system.
Here’s how you might implement a Singleton for a database connection in Node.js:
// DatabaseConnection.js
class DatabaseConnection {
constructor(connectionDetails) {
if (DatabaseConnection.instance) {
return DatabaseConnection.instance;
}
this.connectionDetails = connectionDetails; DatabaseConnection.instance = this;
}
connect() {
console.log(Connecting to database with details: ${this.connectionDetails}
);
// Logic to connect to the database
}
// Ensure the instance is not cloned
clone() {
return this;
}
}
// Usage
const dbConnection1 = new DatabaseConnection("User1:Password1@Host1");
dbConnection1.connect();
const dbConnection2 = new DatabaseConnection("User2:Password2@Host2");
dbConnection2.connect();
console.log(dbConnection1 === dbConnection2); // true
In this Singleton pattern example, the DatabaseConnection
class controls instantiation of itself and ensures that only one instance can exist. If an instance already exists, the constructor returns the existing instance instead of creating a new one. This ensures that multiple requests for a database connection do not create multiple connections, preserving resources and ensuring consistent access to the database.