javascript
examples
examples.js⚡javascript
/**
* 21.4 Design Patterns - Examples
*
* Demonstrating common JavaScript design patterns
*/
// ================================================
// 1. SINGLETON PATTERN
// ================================================
/**
* Singleton - ensures only one instance exists
*/
// ES6 Class-based Singleton
class DatabaseConnection {
constructor(config) {
if (DatabaseConnection.instance) {
return DatabaseConnection.instance;
}
this.config = config;
this.connected = false;
DatabaseConnection.instance = this;
}
connect() {
if (!this.connected) {
console.log('Connecting to database...');
this.connected = true;
}
return this;
}
query(sql) {
if (!this.connected) {
throw new Error('Not connected');
}
console.log(`Executing: ${sql}`);
return { rows: [] };
}
static getInstance(config) {
if (!DatabaseConnection.instance) {
DatabaseConnection.instance = new DatabaseConnection(config);
}
return DatabaseConnection.instance;
}
}
// Module-based Singleton (recommended for JavaScript)
const Logger = (() => {
let instance;
function createLogger() {
const logs = [];
return {
log(message, level = 'info') {
const entry = {
timestamp: new Date().toISOString(),
level,
message,
};
logs.push(entry);
console.log(`[${entry.timestamp}] ${level.toUpperCase()}: ${message}`);
},
getLogs() {
return [...logs];
},
clear() {
logs.length = 0;
},
};
}
return {
getInstance() {
if (!instance) {
instance = createLogger();
}
return instance;
},
};
})();
console.log('='.repeat(50));
console.log('Singleton Pattern:');
console.log('-'.repeat(50));
const db1 = new DatabaseConnection({ host: 'localhost' });
const db2 = new DatabaseConnection({ host: 'remote' }); // Returns same instance!
console.log('Same instance?', db1 === db2);
const logger = Logger.getInstance();
logger.log('Application started');
// ================================================
// 2. FACTORY PATTERN
// ================================================
/**
* Factory - creates objects without exposing creation logic
*/
// Simple Factory
class Vehicle {
constructor(type) {
this.type = type;
}
drive() {
return `Driving a ${this.type}`;
}
}
class Car extends Vehicle {
constructor() {
super('car');
this.wheels = 4;
}
}
class Motorcycle extends Vehicle {
constructor() {
super('motorcycle');
this.wheels = 2;
}
}
class Truck extends Vehicle {
constructor() {
super('truck');
this.wheels = 18;
}
}
class VehicleFactory {
static create(type) {
switch (type.toLowerCase()) {
case 'car':
return new Car();
case 'motorcycle':
return new Motorcycle();
case 'truck':
return new Truck();
default:
throw new Error(`Unknown vehicle type: ${type}`);
}
}
}
// Abstract Factory
class UIFactory {
createButton() {
throw new Error('Must implement');
}
createInput() {
throw new Error('Must implement');
}
createModal() {
throw new Error('Must implement');
}
}
class MaterialUIFactory extends UIFactory {
createButton(text) {
return { type: 'MaterialButton', text, style: 'material' };
}
createInput(placeholder) {
return { type: 'MaterialInput', placeholder, style: 'material' };
}
createModal(title) {
return { type: 'MaterialModal', title, style: 'material' };
}
}
class BootstrapUIFactory extends UIFactory {
createButton(text) {
return { type: 'BootstrapButton', text, style: 'bootstrap' };
}
createInput(placeholder) {
return { type: 'BootstrapInput', placeholder, style: 'bootstrap' };
}
createModal(title) {
return { type: 'BootstrapModal', title, style: 'bootstrap' };
}
}
console.log('\n' + '='.repeat(50));
console.log('Factory Pattern:');
console.log('-'.repeat(50));
const car = VehicleFactory.create('car');
const motorcycle = VehicleFactory.create('motorcycle');
console.log(car.drive(), '- Wheels:', car.wheels);
console.log(motorcycle.drive(), '- Wheels:', motorcycle.wheels);
const uiFactory = new MaterialUIFactory();
console.log('Material Button:', uiFactory.createButton('Click Me'));
// ================================================
// 3. BUILDER PATTERN
// ================================================
/**
* Builder - constructs complex objects step by step
*/
class QueryBuilder {
constructor() {
this.query = {
select: [],
from: null,
where: [],
orderBy: [],
limit: null,
offset: null,
};
}
select(...columns) {
this.query.select = columns.length > 0 ? columns : ['*'];
return this;
}
from(table) {
this.query.from = table;
return this;
}
where(condition, value) {
this.query.where.push({ condition, value });
return this;
}
orderBy(column, direction = 'ASC') {
this.query.orderBy.push({ column, direction });
return this;
}
limit(count) {
this.query.limit = count;
return this;
}
offset(count) {
this.query.offset = count;
return this;
}
build() {
let sql = `SELECT ${this.query.select.join(', ')}`;
sql += ` FROM ${this.query.from}`;
if (this.query.where.length > 0) {
const conditions = this.query.where.map((w) => w.condition).join(' AND ');
sql += ` WHERE ${conditions}`;
}
if (this.query.orderBy.length > 0) {
const orders = this.query.orderBy
.map((o) => `${o.column} ${o.direction}`)
.join(', ');
sql += ` ORDER BY ${orders}`;
}
if (this.query.limit) {
sql += ` LIMIT ${this.query.limit}`;
}
if (this.query.offset) {
sql += ` OFFSET ${this.query.offset}`;
}
return sql;
}
}
// HttpRequest Builder
class HttpRequestBuilder {
constructor() {
this.request = {
method: 'GET',
url: '',
headers: {},
body: null,
timeout: 5000,
};
}
get(url) {
this.request.method = 'GET';
this.request.url = url;
return this;
}
post(url) {
this.request.method = 'POST';
this.request.url = url;
return this;
}
header(key, value) {
this.request.headers[key] = value;
return this;
}
json(data) {
this.request.body = JSON.stringify(data);
this.request.headers['Content-Type'] = 'application/json';
return this;
}
timeout(ms) {
this.request.timeout = ms;
return this;
}
build() {
return { ...this.request };
}
}
console.log('\n' + '='.repeat(50));
console.log('Builder Pattern:');
console.log('-'.repeat(50));
const query = new QueryBuilder()
.select('id', 'name', 'email')
.from('users')
.where('status = ?', 'active')
.where('age > ?', 18)
.orderBy('name', 'ASC')
.limit(10)
.offset(20)
.build();
console.log('Built query:', query);
const request = new HttpRequestBuilder()
.post('https://api.example.com/users')
.header('Authorization', 'Bearer token')
.json({ name: 'John', email: 'john@example.com' })
.timeout(10000)
.build();
console.log('Built request:', request);
// ================================================
// 4. OBSERVER PATTERN
// ================================================
/**
* Observer - one-to-many dependency between objects
*/
class EventEmitter {
constructor() {
this.events = new Map();
}
on(event, callback) {
if (!this.events.has(event)) {
this.events.set(event, new Set());
}
this.events.get(event).add(callback);
// Return unsubscribe function
return () => this.off(event, callback);
}
once(event, callback) {
const wrapper = (...args) => {
callback(...args);
this.off(event, wrapper);
};
return this.on(event, wrapper);
}
off(event, callback) {
if (this.events.has(event)) {
this.events.get(event).delete(callback);
}
}
emit(event, ...args) {
if (this.events.has(event)) {
for (const callback of this.events.get(event)) {
callback(...args);
}
}
}
listenerCount(event) {
return this.events.has(event) ? this.events.get(event).size : 0;
}
}
// Observable Store (React-like state management)
class ObservableStore {
constructor(initialState = {}) {
this.state = initialState;
this.emitter = new EventEmitter();
}
getState() {
return { ...this.state };
}
setState(updates) {
const prevState = this.state;
this.state = { ...this.state, ...updates };
this.emitter.emit('change', this.state, prevState);
}
subscribe(callback) {
return this.emitter.on('change', callback);
}
}
console.log('\n' + '='.repeat(50));
console.log('Observer Pattern:');
console.log('-'.repeat(50));
const emitter = new EventEmitter();
const unsubscribe = emitter.on('userLogin', (user) => {
console.log(`User logged in: ${user.name}`);
});
emitter.on('userLogin', (user) => {
console.log(`Sending welcome email to ${user.email}`);
});
emitter.emit('userLogin', { name: 'John', email: 'john@example.com' });
// Observable store example
const store = new ObservableStore({ count: 0 });
store.subscribe((newState, oldState) => {
console.log(`Count changed: ${oldState.count} → ${newState.count}`);
});
store.setState({ count: 1 });
// ================================================
// 5. STRATEGY PATTERN
// ================================================
/**
* Strategy - interchangeable algorithms
*/
// Payment strategies
const paymentStrategies = {
creditCard: {
name: 'Credit Card',
process(amount, details) {
console.log(
`Processing $${amount} via Credit Card: ${details.cardNumber}`
);
return { success: true, transactionId: 'CC-' + Date.now() };
},
},
paypal: {
name: 'PayPal',
process(amount, details) {
console.log(`Processing $${amount} via PayPal: ${details.email}`);
return { success: true, transactionId: 'PP-' + Date.now() };
},
},
crypto: {
name: 'Cryptocurrency',
process(amount, details) {
console.log(`Processing $${amount} via Crypto: ${details.walletAddress}`);
return { success: true, transactionId: 'CR-' + Date.now() };
},
},
};
class PaymentProcessor {
constructor(strategy = null) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
processPayment(amount, details) {
if (!this.strategy) {
throw new Error('Payment strategy not set');
}
return this.strategy.process(amount, details);
}
}
// Sorting strategies
const sortStrategies = {
byName: (a, b) => a.name.localeCompare(b.name),
byPrice: (a, b) => a.price - b.price,
byRating: (a, b) => b.rating - a.rating,
byDate: (a, b) => new Date(b.date) - new Date(a.date),
};
class ProductList {
constructor(products = []) {
this.products = products;
this.sortStrategy = sortStrategies.byName;
}
setSortStrategy(strategy) {
this.sortStrategy = strategy;
}
sort() {
return [...this.products].sort(this.sortStrategy);
}
}
console.log('\n' + '='.repeat(50));
console.log('Strategy Pattern:');
console.log('-'.repeat(50));
const processor = new PaymentProcessor();
processor.setStrategy(paymentStrategies.creditCard);
console.log(processor.processPayment(100, { cardNumber: '****1234' }));
processor.setStrategy(paymentStrategies.paypal);
console.log(processor.processPayment(50, { email: 'user@paypal.com' }));
// ================================================
// 6. DECORATOR PATTERN
// ================================================
/**
* Decorator - adds behavior dynamically
*/
// Function decorators
function withLogging(fn) {
return function (...args) {
console.log(`Calling ${fn.name} with:`, args);
const result = fn.apply(this, args);
console.log(`${fn.name} returned:`, result);
return result;
};
}
function withTiming(fn) {
return function (...args) {
const start = performance.now();
const result = fn.apply(this, args);
const end = performance.now();
console.log(`${fn.name} took ${(end - start).toFixed(2)}ms`);
return result;
};
}
function withMemoization(fn) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log('Cache hit for:', args);
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// Class decorators (composition)
class Coffee {
constructor() {
this.description = 'Basic Coffee';
this.cost = 2.0;
}
getDescription() {
return this.description;
}
getCost() {
return this.cost;
}
}
class CoffeeDecorator {
constructor(coffee) {
this.coffee = coffee;
}
getDescription() {
return this.coffee.getDescription();
}
getCost() {
return this.coffee.getCost();
}
}
class MilkDecorator extends CoffeeDecorator {
getDescription() {
return super.getDescription() + ', Milk';
}
getCost() {
return super.getCost() + 0.5;
}
}
class SugarDecorator extends CoffeeDecorator {
getDescription() {
return super.getDescription() + ', Sugar';
}
getCost() {
return super.getCost() + 0.25;
}
}
class WhipCreamDecorator extends CoffeeDecorator {
getDescription() {
return super.getDescription() + ', Whip Cream';
}
getCost() {
return super.getCost() + 0.75;
}
}
console.log('\n' + '='.repeat(50));
console.log('Decorator Pattern:');
console.log('-'.repeat(50));
// Function decorator example
function add(a, b) {
return a + b;
}
const decoratedAdd = withLogging(withTiming(add));
decoratedAdd(5, 3);
// Class decorator example
let coffee = new Coffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
coffee = new WhipCreamDecorator(coffee);
console.log(`Order: ${coffee.getDescription()}`);
console.log(`Total: $${coffee.getCost().toFixed(2)}`);
// ================================================
// 7. PROXY PATTERN
// ================================================
/**
* Proxy - controls access to objects
*/
// Validation Proxy
function createValidatedObject(target, validators) {
return new Proxy(target, {
set(obj, prop, value) {
if (validators[prop]) {
const isValid = validators[prop](value);
if (!isValid) {
throw new Error(`Invalid value for ${prop}: ${value}`);
}
}
obj[prop] = value;
return true;
},
});
}
// Logging Proxy
function createLoggingProxy(target, name) {
return new Proxy(target, {
get(obj, prop) {
console.log(`[${name}] Getting ${prop}`);
const value = obj[prop];
return typeof value === 'function' ? value.bind(obj) : value;
},
set(obj, prop, value) {
console.log(`[${name}] Setting ${prop} = ${value}`);
obj[prop] = value;
return true;
},
});
}
// Lazy Loading Proxy
function createLazyLoader(loader) {
let value = null;
let loaded = false;
return new Proxy(
{},
{
get(target, prop) {
if (!loaded) {
value = loader();
loaded = true;
}
return value[prop];
},
}
);
}
// Access Control Proxy
function createAccessControlProxy(target, permissions) {
return new Proxy(target, {
get(obj, prop) {
if (permissions.canRead && permissions.canRead(prop)) {
return obj[prop];
}
throw new Error(`Access denied: Cannot read ${prop}`);
},
set(obj, prop, value) {
if (permissions.canWrite && permissions.canWrite(prop)) {
obj[prop] = value;
return true;
}
throw new Error(`Access denied: Cannot write ${prop}`);
},
});
}
console.log('\n' + '='.repeat(50));
console.log('Proxy Pattern:');
console.log('-'.repeat(50));
// Validation proxy example
const user = createValidatedObject(
{},
{
age: (v) => typeof v === 'number' && v >= 0 && v <= 150,
email: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
}
);
user.age = 25;
console.log('Valid age set:', user.age);
try {
user.age = -5;
} catch (e) {
console.log('Caught validation error:', e.message);
}
// ================================================
// 8. COMMAND PATTERN
// ================================================
/**
* Command - encapsulates actions as objects
*/
class Command {
execute() {
throw new Error('Must implement execute');
}
undo() {
throw new Error('Must implement undo');
}
}
// Text Editor Commands
class InsertTextCommand extends Command {
constructor(editor, text, position) {
super();
this.editor = editor;
this.text = text;
this.position = position;
}
execute() {
this.editor.insertAt(this.position, this.text);
}
undo() {
this.editor.deleteAt(this.position, this.text.length);
}
}
class DeleteTextCommand extends Command {
constructor(editor, position, length) {
super();
this.editor = editor;
this.position = position;
this.length = length;
this.deletedText = null;
}
execute() {
this.deletedText = this.editor.getTextAt(this.position, this.length);
this.editor.deleteAt(this.position, this.length);
}
undo() {
this.editor.insertAt(this.position, this.deletedText);
}
}
// Simple text editor
class TextEditor {
constructor() {
this.content = '';
}
insertAt(position, text) {
this.content =
this.content.slice(0, position) + text + this.content.slice(position);
}
deleteAt(position, length) {
this.content =
this.content.slice(0, position) + this.content.slice(position + length);
}
getTextAt(position, length) {
return this.content.slice(position, position + length);
}
getContent() {
return this.content;
}
}
// Command Invoker with undo/redo
class CommandManager {
constructor() {
this.history = [];
this.redoStack = [];
}
execute(command) {
command.execute();
this.history.push(command);
this.redoStack = []; // Clear redo stack
}
undo() {
if (this.history.length === 0) {
console.log('Nothing to undo');
return;
}
const command = this.history.pop();
command.undo();
this.redoStack.push(command);
}
redo() {
if (this.redoStack.length === 0) {
console.log('Nothing to redo');
return;
}
const command = this.redoStack.pop();
command.execute();
this.history.push(command);
}
}
console.log('\n' + '='.repeat(50));
console.log('Command Pattern:');
console.log('-'.repeat(50));
const editor = new TextEditor();
const commandManager = new CommandManager();
commandManager.execute(new InsertTextCommand(editor, 'Hello', 0));
console.log('After insert:', editor.getContent());
commandManager.execute(new InsertTextCommand(editor, ' World', 5));
console.log('After insert:', editor.getContent());
commandManager.undo();
console.log('After undo:', editor.getContent());
commandManager.redo();
console.log('After redo:', editor.getContent());
// ================================================
// 9. MIDDLEWARE PATTERN
// ================================================
/**
* Middleware - chain of processors
*/
class MiddlewareManager {
constructor() {
this.middlewares = [];
}
use(fn) {
this.middlewares.push(fn);
return this;
}
async execute(context) {
let index = 0;
const next = async () => {
if (index < this.middlewares.length) {
const middleware = this.middlewares[index++];
await middleware(context, next);
}
};
await next();
return context;
}
}
// Example middlewares
const authMiddleware = async (ctx, next) => {
console.log('Auth: Checking authentication...');
if (ctx.headers?.authorization) {
ctx.user = { id: 1, name: 'John' };
}
await next();
};
const loggingMiddleware = async (ctx, next) => {
const start = Date.now();
console.log(`Logger: ${ctx.method} ${ctx.path} started`);
await next();
console.log(`Logger: Completed in ${Date.now() - start}ms`);
};
const validationMiddleware = async (ctx, next) => {
console.log('Validation: Checking request...');
if (!ctx.body?.name) {
ctx.error = 'Name is required';
return; // Don't continue
}
await next();
};
console.log('\n' + '='.repeat(50));
console.log('Middleware Pattern:');
console.log('-'.repeat(50));
const pipeline = new MiddlewareManager();
pipeline
.use(loggingMiddleware)
.use(authMiddleware)
.use(validationMiddleware)
.use(async (ctx, next) => {
console.log('Handler: Processing request');
ctx.result = { success: true, user: ctx.user };
await next();
});
(async () => {
const context = {
method: 'POST',
path: '/api/users',
headers: { authorization: 'Bearer token' },
body: { name: 'John' },
};
await pipeline.execute(context);
console.log('Result:', context.result);
})();
// ================================================
// 10. PUBLISH/SUBSCRIBE PATTERN
// ================================================
/**
* Pub/Sub - loose coupling between publishers and subscribers
*/
class PubSub {
constructor() {
this.topics = new Map();
}
subscribe(topic, callback) {
if (!this.topics.has(topic)) {
this.topics.set(topic, new Set());
}
this.topics.get(topic).add(callback);
// Return unsubscribe function
return () => {
this.topics.get(topic).delete(callback);
};
}
publish(topic, data) {
if (!this.topics.has(topic)) {
return;
}
for (const callback of this.topics.get(topic)) {
// Async to prevent blocking
setTimeout(() => callback(data), 0);
}
}
// Subscribe to pattern (simple wildcard support)
subscribePattern(pattern, callback) {
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
return this.subscribe('__all__', (topic, data) => {
if (regex.test(topic)) {
callback(data, topic);
}
});
}
publishWithPattern(topic, data) {
this.publish(topic, data);
this.publish('__all__', { topic, data });
}
}
console.log('\n' + '='.repeat(50));
console.log('Pub/Sub Pattern:');
console.log('-'.repeat(50));
const pubsub = new PubSub();
// Subscribe to specific topics
pubsub.subscribe('user:created', (data) => {
console.log('Email service: Sending welcome email to', data.email);
});
pubsub.subscribe('user:created', (data) => {
console.log('Analytics: Tracking new user', data.id);
});
pubsub.subscribe('order:placed', (data) => {
console.log('Inventory: Updating stock for order', data.orderId);
});
// Publish events
pubsub.publish('user:created', { id: 1, email: 'john@example.com' });
pubsub.publish('order:placed', { orderId: 'ORD-123', items: 3 });
// ================================================
// EXPORTS
// ================================================
module.exports = {
// Singletons
DatabaseConnection,
Logger,
// Factories
VehicleFactory,
MaterialUIFactory,
BootstrapUIFactory,
// Builders
QueryBuilder,
HttpRequestBuilder,
// Observer
EventEmitter,
ObservableStore,
// Strategy
paymentStrategies,
PaymentProcessor,
sortStrategies,
ProductList,
// Decorators
withLogging,
withTiming,
withMemoization,
Coffee,
MilkDecorator,
SugarDecorator,
WhipCreamDecorator,
// Proxy
createValidatedObject,
createLoggingProxy,
createLazyLoader,
createAccessControlProxy,
// Command
Command,
InsertTextCommand,
DeleteTextCommand,
TextEditor,
CommandManager,
// Middleware
MiddlewareManager,
// Pub/Sub
PubSub,
};