Chapter 5: Design Patterns in JavaScript
Chapter 5 of 15
Chapter 5: Design Patterns in JavaScript
5.1 Creational Patterns
Creational patterns provide mechanisms for object creation that increase flexibility and reuse of existing code.
Singleton Pattern:
// Ensures only one instance exists
const Singleton = (function() {
let instance;
function createInstance() {
return {
data: 'Shared data',
getData: function() {
return this.data;
}
};
}
return {
getInstance: function() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true - same instance
Factory Pattern:
// Creates objects without specifying exact class
function createUser(type, name) {
if (type === 'admin') {
return {
name: name,
role: 'admin',
permissions: ['read', 'write', 'delete']
};
} else if (type === 'user') {
return {
name: name,
role: 'user',
permissions: ['read']
};
}
throw new Error('Invalid user type');
}
const admin = createUser('admin', 'John');
const user = createUser('user', 'Jane');
Builder Pattern:
class QueryBuilder {
constructor() {
this.query = {
select: [],
from: "",
where: [],
orderBy: []
};
}
select(fields) {
this.query.select = fields;
return this;
}
from(table) {
this.query.from = table;
return this;
}
where(condition) {
this.query.where.push(condition);
return this;
}
orderBy(field, direction = 'ASC') {
this.query.orderBy.push({ field, direction });
return this;
}
build() {
return this.query;
}
}
const query = new QueryBuilder()
.select(['id', 'name', 'email'])
.from('users')
.where('status = active')
.orderBy('name', 'ASC')
.build();
5.2 Structural Patterns
Structural patterns explain how to assemble objects and classes into larger structures while keeping them flexible and efficient.
Module Pattern:
const MyModule = (function() {
// Private variables and functions
let privateVar = 0;
function privateFunction() {
return 'Private';
}
// Public API
return {
publicMethod: function() {
privateVar++;
return privateFunction() + ' - ' + privateVar;
},
getPrivateVar: function() {
return privateVar;
}
};
})();
console.log(MyModule.publicMethod()); // "Private - 1"
// privateVar and privateFunction are not accessible
Observer Pattern:
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('userLogin', (user) => {
console.log(`User ${user.name} logged in`);
});
emitter.emit('userLogin', { name: 'John' });
5.3 Behavioral Patterns
Behavioral patterns are concerned with algorithms and the assignment of responsibilities between objects.
Strategy Pattern:
// Encapsulates algorithms and makes them interchangeable
const paymentStrategies = {
creditCard: function(amount) {
return `Paid $${amount} via Credit Card`;
},
paypal: function(amount) {
return `Paid $${amount} via PayPal`;
},
crypto: function(amount) {
return `Paid $${amount} via Cryptocurrency`;
}
};
function PaymentProcessor(strategy) {
this.strategy = strategy;
this.pay = function(amount) {
return paymentStrategies[this.strategy](amount);
};
}
const payment = new PaymentProcessor('paypal');
console.log(payment.pay(100)); // "Paid $100 via PayPal"
5.4 MVC Pattern
// Model - Data and business logic
class Model {
constructor() {
this.data = [];
}
add(item) {
this.data.push(item);
this.notify();
}
notify() {
// Notify view of changes
}
}
// View - Presentation layer
class View {
render(data) {
console.log('Rendering:', data);
}
}
// Controller - Mediates between Model and View
class Controller {
constructor(model, view) {
this.model = model;
this.view = view;
}
addItem(item) {
this.model.add(item);
this.view.render(this.model.data);
}
}