JavaScript Design Patterns
Design patterns are proven solutions to common problems. Let's explore the most useful ones.
Singleton Pattern
Ensure only one instance exists:
class Database {
static instance = null;
constructor() {
if (Database.instance) {
return Database.instance;
}
this.connection = this.connect();
Database.instance = this;
}
connect() {
console.log('Connecting to database...');
return { connected: true };
}
}
const db1 = new Database();
const db2 = new Database();
console.log(db1 === db2); // true
Factory Pattern
Create objects without specifying the exact class:
class UserFactory {
static createUser(type) {
switch (type) {
case 'admin':
return new AdminUser();
case 'guest':
return new GuestUser();
default:
return new RegularUser();
}
}
}
const admin = UserFactory.createUser('admin');
const guest = UserFactory.createUser('guest');
Observer Pattern
Subscribe to events:
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event, data) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(data));
}
}
off(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event]
.filter(cb => cb !== callback);
}
}
}
const emitter = new EventEmitter();
emitter.on('userCreated', user => console.log('New user:', user));
emitter.emit('userCreated', { name: 'John' });
Module Pattern
Encapsulate private state:
const Counter = (function() {
let count = 0; // Private
return {
increment() {
return ++count;
},
decrement() {
return --count;
},
getCount() {
return count;
}
};
})();
Counter.increment();
Counter.increment();
console.log(Counter.getCount()); // 2
console.log(Counter.count); // undefined
Strategy Pattern
Swap algorithms at runtime:
const paymentStrategies = {
creditCard: (amount) => {
console.log(`Paying ${amount} with credit card`);
},
paypal: (amount) => {
console.log(`Paying ${amount} with PayPal`);
},
crypto: (amount) => {
console.log(`Paying ${amount} with crypto`);
}
};
function processPayment(amount, strategy) {
paymentStrategies[strategy](amount);
}
processPayment(100, 'paypal');
Decorator Pattern
Add behavior dynamically:
function withLogging(fn) {
return function(...args) {
console.log(`Calling ${fn.name} with`, args);
const result = fn.apply(this, args);
console.log(`Result:`, result);
return result;
};
}
const add = (a, b) => a + b;
const loggedAdd = withLogging(add);
loggedAdd(2, 3);
Conclusion
These patterns solve real problems. Learn them, but don't force them where they don't fit.