Introduction
This week, I learned how Design Patterns can help organize JavaScript applications and make code easier to maintain, reuse, and extend.
For Task 01, I implemented three commonly used design patterns:
- Observer Pattern
- Singleton Pattern
- Factory Pattern
Instead of only learning the theory, I implemented each pattern using a small practical example and tested the implementations using Node.js's built-in assert module.
What Are Design Patterns?
Design patterns are reusable solutions to common software design problems.
They are not ready-made code that we copy into every project. Instead, they provide a proven structure for solving certain types of problems.
For example:
- Need one object to notify multiple objects? → Observer
- Need only one shared instance of something? → Singleton
- Need to create different types of objects without exposing the creation logic? → Factory
1. Observer Pattern
What is the Observer Pattern?
The Observer Pattern is useful when one object needs to notify multiple other objects whenever something happens.
A simple real-world example is YouTube subscriptions.
When a channel uploads a new video:
YouTube Channel
|
| notification
↓
Subscribers
↓ ↓ ↓
User1 User2 User3
The channel doesn't need to manually manage what every subscriber does. It simply sends an event, and the subscribers respond.
My Implementation
I created a small event emitter:
class MiniEventEmitter {
constructor() {
this.events = {};
}
on(event, listener) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
}
off(event, listener) {
if (!this.events[event]) return;
this.events[event] = this.events[event].filter(
(item) => item !== listener
);
}
emit(event, data) {
if (!this.events[event]) return;
this.events[event].forEach((listener) => {
listener(data);
});
}
once(event, listener) {
const wrapper = (data) => {
listener(data);
this.off(event, wrapper);
};
this.on(event, wrapper);
}
}
The class supports:
-
on()→ subscribe to an event -
off()→ unsubscribe -
emit()→ trigger an event -
once()→ execute a listener only once
Example
const emitter = new MiniEventEmitter();
emitter.on("login", (user) => {
console.log(`${user} logged in`);
});
emitter.emit("login", "Koushik");
Output:
Koushik logged in
Why is this useful?
This pattern is useful when different parts of an application need to react to the same event.
Examples include:
- Notifications
- UI events
- Logging
- Message systems
- Real-time applications
2. Singleton Pattern
What is the Singleton Pattern?
The Singleton Pattern ensures that a class has only one instance throughout the application.
For example, an application configuration manager usually doesn't need multiple instances.
Instead of:
Config 1
Config 2
Config 3
we want:
Config
↑
┌────┼────┐
↓ ↓ ↓
App Service API
Everyone uses the same configuration object.
My Implementation
I created a singleton configuration manager:
class ConfigManager {
constructor() {
if (ConfigManager.instance) {
return ConfigManager.instance;
}
this.config = {};
ConfigManager.instance = this;
}
set(key, value) {
this.config[key] = value;
}
get(key) {
return this.config[key];
}
}
module.exports = new ConfigManager();
Now whenever the module is imported, the same instance is reused.
Example
const config = require("./singleton-config");
config.set("environment", "development");
console.log(config.get("environment"));
Output:
development
Why is this useful?
Singletons can be useful for shared resources such as:
- Configuration
- Logging
- Application state
- Connection managers
- Caches
However, Singleton should not be used everywhere because excessive global shared state can make applications harder to test and maintain.
3. Factory Pattern
What is the Factory Pattern?
The Factory Pattern is used when we want to create different types of objects without putting the object creation logic everywhere in the application.
For example, imagine a notification system.
We may have:
Notification Factory
|
┌─────┼─────┐
↓ ↓ ↓
Email SMS Push
The application only needs to tell the factory which notification it wants.
My Implementation
I created notification classes:
class EmailNotification {
send(message) {
return `Email: ${message}`;
}
}
class SMSNotification {
send(message) {
return `SMS: ${message}`;
}
}
class PushNotification {
send(message) {
return `Push: ${message}`;
}
}
Then I created a factory:
class NotificationFactory {
static create(type) {
switch (type) {
case "email":
return new EmailNotification();
case "sms":
return new SMSNotification();
case "push":
return new PushNotification();
default:
throw new Error("Unsupported notification type");
}
}
}
Now the application can simply do:
const notification =
NotificationFactory.create("email");
console.log(notification.send("Hello!"));
Output:
Email: Hello!
The calling code doesn't need to know how the EmailNotification object is created.
Testing
For testing, I used Node.js's built-in assert module.
Example:
const assert = require("assert");
assert.strictEqual(
notification.send("Hello!"),
"Email: Hello!"
);
I tested the important behaviors of each pattern.
Observer tests
I verified:
- Listeners can subscribe.
- Events are emitted correctly.
- Listeners can unsubscribe.
-
once()listeners execute only once.
Singleton tests
I verified that:
const config1 = require("./singleton-config");
const config2 = require("./singleton-config");
refer to the same instance.
Factory tests
I verified that the factory creates the correct notification type and rejects unsupported types.
Project Structure
The final Task 01 structure was:
week-06/
└── Task-01/
├── README.md
├── mini-event-emitter.js
├── singleton-config.js
├── notification-factory.js
└── test.js
Each file had a specific responsibility:
| File | Purpose |
|---|---|
mini-event-emitter.js |
Observer Pattern |
singleton-config.js |
Singleton Pattern |
notification-factory.js |
Factory Pattern |
test.js |
Tests |
README.md |
Documentation |
What I Learned
This task helped me understand that design patterns are mainly about structuring code to solve recurring problems.
Observer
I learned how objects can communicate through events without being tightly coupled.
Singleton
I learned how to maintain one shared instance when multiple parts of an application need the same resource.
Factory
I learned how to separate object creation from the code that uses those objects.
Key Takeaways
The main concepts I learned from this task are:
Observer → Event-based communication
Singleton → One shared instance
Factory → Centralized object creation
These patterns are especially useful when applications become larger and different parts of the system need clear responsibilities.
Conclusion
Week 06 Task 01 gave me practical experience with three important JavaScript design patterns: Observer, Singleton, and Factory.
Instead of just reading about these patterns, I implemented each one from scratch and wrote tests to verify their behavior.
This exercise helped me understand how design patterns can improve code organization, reusability, maintainability, and separation of responsibilities.
I’ll be building on these concepts in the next tasks by exploring more design patterns and applying them to practical problems.
Top comments (0)