Design Pattern Implementation - Complete Guide
Published: September 25, 2024 | Reading time: 32 minutes
Design Patterns Overview
Design patterns provide proven solutions to common software design problems:
Design Pattern Benefits
# Design Pattern Benefits
- Proven solutions to common problems
- Improved code reusability
- Better maintainability
- Enhanced communication
- Reduced development time
- Higher code quality
- Easier testing and debugging
Creational Patterns
Singleton Pattern
Singleton Pattern Implementation
# Singleton Pattern Implementation
# 1. Basic Singleton
class DatabaseConnection {
constructor() {
if (DatabaseConnection.instance) {
return DatabaseConnection.instance;
}
this.host = 'localhost';
this.port = 3306;
this.database = 'myapp';
this.connection = null;
DatabaseConnection.instance = this;
}
connect() {
if (!this.connection) {
this.connection = this.createConnection();
}
return this.connection;
}
createConnection() {
// Simulate database connection
console.log('Creating database connection...');
return {
host: this.host,
port: this.port,
database: this.database,
connected: true
};
}
disconnect() {
if (this.connection) {
this.connection.connected = false;
this.connection = null;
}
}
}
# Usage
const db1 = new DatabaseConnection();
const db2 = new DatabaseConnection();
console.log(db1 === db2); // true
# 2. Lazy Singleton
class LazySingleton {
static getInstance() {
if (!LazySingleton.instance) {
LazySingleton.instance = new LazySingleton();
}
return LazySingleton.instance;
}
constructor() {
if (LazySingleton.instance) {
throw new Error('Use getInstance() method instead');
}
this.data = [];
this.initialized = false;
}
initialize() {
if (!this.initialized) {
this.data = this.loadData();
this.initialized = true;
}
}
loadData() {
// Simulate data loading
return ['data1', 'data2', 'data3'];
}
getData() {
this.initialize();
return this.data;
}
}
# Usage
const instance1 = LazySingleton.getInstance();
const instance2 = LazySingleton.getInstance();
console.log(instance1 === instance2); // true
# 3. Thread-Safe Singleton (for Node.js)
class ThreadSafeSingleton {
static getInstance() {
if (!ThreadSafeSingleton.instance) {
ThreadSafeSingleton.instance = new ThreadSafeSingleton();
}
return ThreadSafeSingleton.instance;
}
constructor() {
if (ThreadSafeSingleton.instance) {
throw new Error('Use getInstance() method instead');
}
this.cache = new Map();
this.locks = new Map();
}
async get(key) {
if (this.locks.has(key)) {
await this.locks.get(key);
}
return this.cache.get(key);
}
async set(key, value) {
const lock = new Promise(resolve => {
this.locks.set(key, resolve);
});
this.cache.set(key, value);
this.locks.delete(key);
}
}
# 4. Singleton with Configuration
class ConfigManager {
static getInstance() {
if (!ConfigManager.instance) {
ConfigManager.instance = new ConfigManager();
}
return ConfigManager.instance;
}
constructor() {
if (ConfigManager.instance) {
throw new Error('Use getInstance() method instead');
}
this.config = {};
this.loaded = false;
}
load(config) {
if (!this.loaded) {
this.config = { ...this.config, ...config };
this.loaded = true;
}
}
get(key) {
return this.config[key];
}
set(key, value) {
this.config[key] = value;
}
getAll() {
return { ...this.config };
}
}
# Usage
const config = ConfigManager.getInstance();
config.load({
database: {
host: 'localhost',
port: 3306
},
api: {
baseUrl: 'https://api.example.com',
timeout: 5000
}
});
console.log(config.get('database.host')); // localhost
Factory Pattern
Factory Pattern Implementation
# Factory Pattern Implementation
# 1. Simple Factory
class VehicleFactory {
static createVehicle(type, options = {}) {
switch (type) {
case 'car':
return new Car(options);
case 'truck':
return new Truck(options);
case 'motorcycle':
return new Motorcycle(options);
default:
throw new Error(`Unknown vehicle type: ${type}`);
}
}
}
class Vehicle {
constructor(options) {
this.brand = options.brand || 'Unknown';
this.model = options.model || 'Unknown';
this.year = options.year || new Date().getFullYear();
}
start() {
throw new Error('start method must be implemented');
}
stop() {
throw new Error('stop method must be implemented');
}
}
class Car extends Vehicle {
constructor(options) {
super(options);
this.doors = options.doors || 4;
this.fuelType = options.fuelType || 'gasoline';
}
start() {
console.log(`${this.brand} ${this.model} car started`);
}
stop() {
console.log(`${this.brand} ${this.model} car stopped`);
}
}
class Truck extends Vehicle {
constructor(options) {
super(options);
this.capacity = options.capacity || 1000;
this.fuelType = options.fuelType || 'diesel';
}
start() {
console.log(`${this.brand} ${this.model} truck started`);
}
stop() {
console.log(`${this.brand} ${this.model} truck stopped`);
}
}
class Motorcycle extends Vehicle {
constructor(options) {
super(options);
this.engineSize = options.engineSize || 250;
this.fuelType = options.fuelType || 'gasoline';
}
start() {
console.log(`${this.brand} ${this.model} motorcycle started`);
}
stop() {
console.log(`${this.brand} ${this.model} motorcycle stopped`);
}
}
# Usage
const car = VehicleFactory.createVehicle('car', {
brand: 'Toyota',
model: 'Camry',
year: 2023,
doors: 4
});
const truck = VehicleFactory.createVehicle('truck', {
brand: 'Ford',
model: 'F-150',
year: 2023,
capacity: 1500
});
# 2. Abstract Factory
class AbstractFactory {
createProductA() {
throw new Error('createProductA method must be implemented');
}
createProductB() {
throw new Error('createProductB method must be implemented');
}
}
class ConcreteFactory1 extends AbstractFactory {
createProductA() {
return new ConcreteProductA1();
}
createProductB() {
return new ConcreteProductB1();
}
}
class ConcreteFactory2 extends AbstractFactory {
createProductA() {
return new ConcreteProductA2();
}
createProductB() {
return new ConcreteProductB2();
}
}
class AbstractProductA {
operation() {
throw new Error('operation method must be implemented');
}
}
class AbstractProductB {
operation() {
throw new Error('operation method must be implemented');
}
}
class ConcreteProductA1 extends AbstractProductA {
operation() {
return 'ConcreteProductA1 operation';
}
}
class ConcreteProductA2 extends AbstractProductA {
operation() {
return 'ConcreteProductA2 operation';
}
}
class ConcreteProductB1 extends AbstractProductB {
operation() {
return 'ConcreteProductB1 operation';
}
}
class ConcreteProductB2 extends AbstractProductB {
operation() {
return 'ConcreteProductB2 operation';
}
}
# Usage
const factory1 = new ConcreteFactory1();
const productA1 = factory1.createProductA();
const productB1 = factory1.createProductB();
const factory2 = new ConcreteFactory2();
const productA2 = factory2.createProductA();
const productB2 = factory2.createProductB();
# 3. Factory Method
class DocumentFactory {
createDocument(type) {
switch (type) {
case 'pdf':
return new PDFDocument();
case 'word':
return new WordDocument();
case 'excel':
return new ExcelDocument();
default:
throw new Error(`Unknown document type: ${type}`);
}
}
}
class Document {
constructor() {
this.content = '';
this.createdAt = new Date();
}
addContent(content) {
this.content += content;
}
save() {
throw new Error('save method must be implemented');
}
print() {
throw new Error('print method must be implemented');
}
}
class PDFDocument extends Document {
save() {
console.log('Saving PDF document...');
return `PDF_${Date.now()}.pdf`;
}
print() {
console.log('Printing PDF document...');
}
}
class WordDocument extends Document {
save() {
console.log('Saving Word document...');
return `Document_${Date.now()}.docx`;
}
print() {
console.log('Printing Word document...');
}
}
class ExcelDocument extends Document {
save() {
console.log('Saving Excel document...');
return `Spreadsheet_${Date.now()}.xlsx`;
}
print() {
console.log('Printing Excel document...');
}
}
# Usage
const docFactory = new DocumentFactory();
const pdfDoc = docFactory.createDocument('pdf');
pdfDoc.addContent('Hello World');
const filename = pdfDoc.save();
Structural Patterns
Adapter Pattern
Adapter Pattern Implementation
# Adapter Pattern Implementation
# 1. Object Adapter
class OldPaymentSystem {
processPayment(amount, cardNumber, expiryDate, cvv) {
console.log(`Processing payment of $${amount} with card ending in ${cardNumber.slice(-4)}`);
return {
success: true,
transactionId: `OLD_${Date.now()}`,
amount: amount
};
}
}
class NewPaymentSystem {
processPayment(paymentData) {
console.log(`Processing payment of $${paymentData.amount} with card ending in ${paymentData.cardNumber.slice(-4)}`);
return {
success: true,
transactionId: `NEW_${Date.now()}`,
amount: paymentData.amount,
timestamp: new Date()
};
}
}
class PaymentAdapter {
constructor(paymentSystem) {
this.paymentSystem = paymentSystem;
}
processPayment(amount, cardNumber, expiryDate, cvv) {
if (this.paymentSystem instanceof OldPaymentSystem) {
return this.paymentSystem.processPayment(amount, cardNumber, expiryDate, cvv);
} else if (this.paymentSystem instanceof NewPaymentSystem) {
const paymentData = {
amount: amount,
cardNumber: cardNumber,
expiryDate: expiryDate,
cvv: cvv
};
return this.paymentSystem.processPayment(paymentData);
}
}
}
# Usage
const oldSystem = new OldPaymentSystem();
const newSystem = new NewPaymentSystem();
const oldAdapter = new PaymentAdapter(oldSystem);
const newAdapter = new PaymentAdapter(newSystem);
const result1 = oldAdapter.processPayment(100, '1234567890123456', '12/25', '123');
const result2 = newAdapter.processPayment(100, '1234567890123456', '12/25', '123');
# 2. Class Adapter
class LegacyDatabase {
query(sql) {
console.log(`Executing legacy query: ${sql}`);
return {
rows: [
{ id: 1, name: 'John', email: 'john@example.com' },
{ id: 2, name: 'Jane', email: 'jane@example.com' }
],
count: 2
};
}
}
class ModernDatabase {
find(table, conditions = {}) {
console.log(`Finding records in ${table} with conditions:`, conditions);
return [
{ id: 1, name: 'John', email: 'john@example.com' },
{ id: 2, name: 'Jane', email: 'jane@example.com' }
];
}
save(table, data) {
console.log(`Saving data to ${table}:`, data);
return { id: Date.now(), ...data };
}
}
class DatabaseAdapter extends LegacyDatabase {
constructor(modernDatabase) {
super();
this.modernDatabase = modernDatabase;
}
query(sql) {
// Convert SQL to modern database calls
const table = this.extractTable(sql);
const conditions = this.extractConditions(sql);
if (sql.toLowerCase().includes('select')) {
const results = this.modernDatabase.find(table, conditions);
return {
rows: results,
count: results.length
};
} else if (sql.toLowerCase().includes('insert')) {
const data = this.extractInsertData(sql);
const result = this.modernDatabase.save(table, data);
return {
rows: [result],
count: 1
};
}
return super.query(sql);
}
extractTable(sql) {
const match = sql.match(/from\s+(\w+)/i);
return match ? match[1] : 'unknown';
}
extractConditions(sql) {
const conditions = {};
const whereMatch = sql.match(/where\s+(.+)/i);
if (whereMatch) {
const whereClause = whereMatch[1];
const conditionMatch = whereClause.match(/(\w+)\s*=\s*'([^']+)'/);
if (conditionMatch) {
conditions[conditionMatch[1]] = conditionMatch[2];
}
}
return conditions;
}
extractInsertData(sql) {
const match = sql.match(/insert\s+into\s+\w+\s+\(([^)]+)\)\s+values\s+\(([^)]+)\)/i);
if (match) {
const columns = match[1].split(',').map(col => col.trim());
const values = match[2].split(',').map(val => val.trim().replace(/'/g, ''));
const data = {};
columns.forEach((col, index) => {
data[col] = values[index];
});
return data;
}
return {};
}
}
# Usage
const modernDb = new ModernDatabase();
const adapter = new DatabaseAdapter(modernDb);
const result = adapter.query("SELECT * FROM users WHERE name = 'John'");
console.log(result);
# 3. Interface Adapter
class ThirdPartyAPI {
async fetchData(endpoint) {
console.log(`Fetching data from ${endpoint}`);
return {
data: [
{ id: 1, title: 'Item 1', description: 'Description 1' },
{ id: 2, title: 'Item 2', description: 'Description 2' }
],
status: 'success',
timestamp: new Date()
};
}
}
class InternalAPI {
async getItems() {
console.log('Getting items from internal API');
return [
{ id: 1, name: 'Item 1', desc: 'Description 1' },
{ id: 2, name: 'Item 2', desc: 'Description 2' }
];
}
}
class APIAdapter {
constructor(thirdPartyAPI) {
this.thirdPartyAPI = thirdPartyAPI;
}
async getItems() {
const response = await this.thirdPartyAPI.fetchData('/items');
// Transform third-party response to internal format
return response.data.map(item => ({
id: item.id,
name: item.title,
desc: item.description
}));
}
}
# Usage
const thirdPartyAPI = new ThirdPartyAPI();
const adapter = new APIAdapter(thirdPartyAPI);
const items = await adapter.getItems();
Decorator Pattern
Decorator Pattern Implementation
# Decorator Pattern Implementation
# 1. Basic Decorator
class Coffee {
getCost() {
return 2.0;
}
getDescription() {
return 'Simple coffee';
}
}
class CoffeeDecorator {
constructor(coffee) {
this.coffee = coffee;
}
getCost() {
return this.coffee.getCost();
}
getDescription() {
return this.coffee.getDescription();
}
}
class MilkDecorator extends CoffeeDecorator {
getCost() {
return this.coffee.getCost() + 0.5;
}
getDescription() {
return this.coffee.getDescription() + ', milk';
}
}
class SugarDecorator extends CoffeeDecorator {
getCost() {
return this.coffee.getCost() + 0.2;
}
getDescription() {
return this.coffee.getDescription() + ', sugar';
}
}
class VanillaDecorator extends CoffeeDecorator {
getCost() {
return this.coffee.getCost() + 0.3;
}
getDescription() {
return this.coffee.getDescription() + ', vanilla';
}
}
# Usage
let coffee = new Coffee();
console.log(`${coffee.getDescription()}: $${coffee.getCost()}`);
coffee = new MilkDecorator(coffee);
console.log(`${coffee.getDescription()}: $${coffee.getCost()}`);
coffee = new SugarDecorator(coffee);
console.log(`${coffee.getDescription()}: $${coffee.getCost()}`);
coffee = new VanillaDecorator(coffee);
console.log(`${coffee.getDescription()}: $${coffee.getCost()}`);
# 2. Function Decorator
function withLogging(fn) {
return function(...args) {
console.log(`Calling ${fn.name} with arguments:`, args);
const result = fn.apply(this, args);
console.log(`${fn.name} returned:`, result);
return result;
};
}
function withTiming(fn) {
return function(...args) {
const start = Date.now();
const result = fn.apply(this, args);
const end = Date.now();
console.log(`${fn.name} took ${end - start}ms`);
return result;
};
}
function withRetry(fn, maxRetries = 3) {
return function(...args) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return fn.apply(this, args);
} catch (error) {
lastError = error;
console.log(`Attempt ${i + 1} failed:`, error.message);
if (i < maxRetries - 1) {
console.log('Retrying...');
}
}
}
throw lastError;
};
}
function withCaching(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log('Returning cached result');
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
console.log('Cached result');
return result;
};
}
# Usage
function expensiveCalculation(n) {
// Simulate expensive calculation
let result = 0;
for (let i = 0; i < n * 1000000; i++) {
result += Math.random();
}
return result;
}
const decoratedFunction = withLogging(withTiming(withCaching(expensiveCalculation)));
const result = decoratedFunction(100);
# 3. Class Decorator
function withValidation(schema) {
return function(target) {
const originalMethod = target.prototype.validate;
target.prototype.validate = function(data) {
const errors = [];
for (const field in schema) {
const rules = schema[field];
if (rules.required && !data[field]) {
errors.push(`${field} is required`);
}
if (data[field] && rules.type) {
if (rules.type === 'string' && typeof data[field] !== 'string') {
errors.push(`${field} must be a string`);
} else if (rules.type === 'number' && typeof data[field] !== 'number') {
errors.push(`${field} must be a number`);
} else if (rules.type === 'email' && !this.isValidEmail(data[field])) {
errors.push(`${field} must be a valid email`);
}
}
if (data[field] && rules.minLength && data[field].length < rules.minLength) {
errors.push(`${field} must be at least ${rules.minLength} characters`);
}
if (data[field] && rules.maxLength && data[field].length > rules.maxLength) {
errors.push(`${field} must be no more than ${rules.maxLength} characters`);
}
}
if (errors.length > 0) {
throw new Error(`Validation failed: ${errors.join(', ')}`);
}
return originalMethod ? originalMethod.call(this, data) : data;
};
return target;
};
}
function withLogging(target) {
const originalMethods = {};
for (const methodName of Object.getOwnPropertyNames(target.prototype)) {
if (typeof target.prototype[methodName] === 'function' && methodName !== 'constructor') {
originalMethods[methodName] = target.prototype[methodName];
target.prototype[methodName] = function(...args) {
console.log(`Calling ${methodName} with arguments:`, args);
const result = originalMethods[methodName].apply(this, args);
console.log(`${methodName} returned:`, result);
return result;
};
}
}
return target;
}
@withValidation({
name: { required: true, type: 'string', minLength: 2, maxLength: 50 },
email: { required: true, type: 'email' },
age: { type: 'number' }
})
@withLogging
class UserService {
constructor() {
this.users = [];
}
createUser(userData) {
const user = {
id: Date.now(),
...userData,
createdAt: new Date()
};
this.users.push(user);
return user;
}
getUserById(id) {
return this.users.find(user => user.id === id);
}
updateUser(id, userData) {
const user = this.getUserById(id);
if (user) {
Object.assign(user, userData);
user.updatedAt = new Date();
}
return user;
}
deleteUser(id) {
const index = this.users.findIndex(user => user.id === id);
if (index !== -1) {
this.users.splice(index, 1);
return true;
}
return false;
}
isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
}
# Usage
const userService = new UserService();
const user = userService.createUser({
name: 'John Doe',
email: 'john@example.com',
age: 30
});
Behavioral Patterns
Observer Pattern
Observer Pattern Implementation
# Observer Pattern Implementation
# 1. Basic Observer Pattern
class Subject {
constructor() {
this.observers = [];
}
addObserver(observer) {
this.observers.push(observer);
}
removeObserver(observer) {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
}
notifyObservers(data) {
this.observers.forEach(observer => observer.update(data));
}
}
class Observer {
constructor(name) {
this.name = name;
}
update(data) {
console.log(`${this.name} received update:`, data);
}
}
# Usage
const subject = new Subject();
const observer1 = new Observer('Observer 1');
const observer2 = new Observer('Observer 2');
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.notifyObservers({ message: 'Hello World' });
# 2. Event System
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
off(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
}
emit(event, data) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(data));
}
}
once(event, callback) {
const onceCallback = (data) => {
callback(data);
this.off(event, onceCallback);
};
this.on(event, onceCallback);
}
}
# Usage
const emitter = new EventEmitter();
emitter.on('user:created', (user) => {
console.log('User created:', user);
});
emitter.on('user:created', (user) => {
console.log('Sending welcome email to:', user.email);
});
emitter.emit('user:created', { id: 1, name: 'John', email: 'john@example.com' });
# 3. State Management
class StateManager {
constructor(initialState = {}) {
this.state = initialState;
this.subscribers = [];
}
getState() {
return { ...this.state };
}
setState(newState) {
const prevState = { ...this.state };
this.state = { ...this.state, ...newState };
this.notifySubscribers(prevState, this.state);
}
subscribe(callback) {
this.subscribers.push(callback);
return () => {
const index = this.subscribers.indexOf(callback);
if (index !== -1) {
this.subscribers.splice(index, 1);
}
};
}
notifySubscribers(prevState, newState) {
this.subscribers.forEach(callback => callback(prevState, newState));
}
}
# Usage
const stateManager = new StateManager({ count: 0, user: null });
const unsubscribe = stateManager.subscribe((prevState, newState) => {
console.log('State changed:', { prevState, newState });
});
stateManager.setState({ count: 1 });
stateManager.setState({ user: { id: 1, name: 'John' } });
# 4. Model-View Pattern
class Model extends Subject {
constructor() {
super();
this.data = {};
}
setData(key, value) {
const oldValue = this.data[key];
this.data[key] = value;
this.notifyObservers({
type: 'dataChanged',
key: key,
oldValue: oldValue,
newValue: value
});
}
getData(key) {
return this.data[key];
}
getAllData() {
return { ...this.data };
}
}
class View extends Observer {
constructor(model, elementId) {
super();
this.model = model;
this.element = document.getElementById(elementId);
this.model.addObserver(this);
}
update(data) {
if (data.type === 'dataChanged') {
this.render();
}
}
render() {
const data = this.model.getAllData();
this.element.innerHTML = this.generateHTML(data);
}
generateHTML(data) {
return Object.keys(data).map(key =>
`${key}: ${data[key]}`
).join('');
}
}
# Usage
const model = new Model();
const view = new View(model, 'app');
model.setData('title', 'Hello World');
model.setData('count', 42);
# 5. Pub-Sub Pattern
class PubSub {
constructor() {
this.topics = {};
}
subscribe(topic, callback) {
if (!this.topics[topic]) {
this.topics[topic] = [];
}
this.topics[topic].push(callback);
return {
unsubscribe: () => {
this.topics[topic] = this.topics[topic].filter(cb => cb !== callback);
}
};
}
publish(topic, data) {
if (this.topics[topic]) {
this.topics[topic].forEach(callback => callback(data));
}
}
publishAsync(topic, data) {
if (this.topics[topic]) {
this.topics[topic].forEach(callback => {
setTimeout(() => callback(data), 0);
});
}
}
}
# Usage
const pubsub = new PubSub();
const subscription1 = pubsub.subscribe('user:login', (user) => {
console.log('User logged in:', user);
});
const subscription2 = pubsub.subscribe('user:login', (user) => {
console.log('Updating user session:', user);
});
pubsub.publish('user:login', { id: 1, name: 'John', email: 'john@example.com' });
subscription1.unsubscribe();
pubsub.publish('user:login', { id: 2, name: 'Jane', email: 'jane@example.com' });
Strategy Pattern
Strategy Pattern Implementation
# Strategy Pattern Implementation
# 1. Basic Strategy Pattern
class PaymentStrategy {
pay(amount) {
throw new Error('pay method must be implemented');
}
}
class CreditCardStrategy extends PaymentStrategy {
constructor(cardNumber, expiryDate, cvv) {
super();
this.cardNumber = cardNumber;
this.expiryDate = expiryDate;
this.cvv = cvv;
}
pay(amount) {
console.log(`Paying $${amount} with credit card ending in ${this.cardNumber.slice(-4)}`);
return {
success: true,
transactionId: `CC_${Date.now()}`,
amount: amount
};
}
}
class PayPalStrategy extends PaymentStrategy {
constructor(email) {
super();
this.email = email;
}
pay(amount) {
console.log(`Paying $${amount} with PayPal account ${this.email}`);
return {
success: true,
transactionId: `PP_${Date.now()}`,
amount: amount
};
}
}
class BankTransferStrategy extends PaymentStrategy {
constructor(accountNumber, routingNumber) {
super();
this.accountNumber = accountNumber;
this.routingNumber = routingNumber;
}
pay(amount) {
console.log(`Paying $${amount} with bank transfer from account ${this.accountNumber}`);
return {
success: true,
transactionId: `BT_${Date.now()}`,
amount: amount
};
}
}
class PaymentProcessor {
constructor() {
this.strategy = null;
}
setStrategy(strategy) {
this.strategy = strategy;
}
processPayment(amount) {
if (!this.strategy) {
throw new Error('Payment strategy not set');
}
return this.strategy.pay(amount);
}
}
# Usage
const processor = new PaymentProcessor();
processor.setStrategy(new CreditCardStrategy('1234567890123456', '12/25', '123'));
processor.processPayment(100);
processor.setStrategy(new PayPalStrategy('user@example.com'));
processor.processPayment(50);
processor.setStrategy(new BankTransferStrategy('123456789', '987654321'));
processor.processPayment(200);
# 2. Sorting Strategy
class SortingStrategy {
sort(array) {
throw new Error('sort method must be implemented');
}
}
class BubbleSortStrategy extends SortingStrategy {
sort(array) {
console.log('Using bubble sort');
const arr = [...array];
const n = arr.length;
for (let i = 0; i < n - 1; i++) {
for (let j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
}
class QuickSortStrategy extends SortingStrategy {
sort(array) {
console.log('Using quick sort');
const arr = [...array];
if (arr.length <= 1) {
return arr;
}
const pivot = arr[Math.floor(arr.length / 2)];
const left = arr.filter(x => x < pivot);
const right = arr.filter(x => x > pivot);
const equal = arr.filter(x => x === pivot);
return [...this.sort(left), ...equal, ...this.sort(right)];
}
}
class MergeSortStrategy extends SortingStrategy {
sort(array) {
console.log('Using merge sort');
const arr = [...array];
if (arr.length <= 1) {
return arr;
}
const mid = Math.floor(arr.length / 2);
const left = this.sort(arr.slice(0, mid));
const right = this.sort(arr.slice(mid));
return this.merge(left, right);
}
merge(left, right) {
const result = [];
let i = 0, j = 0;
while (i < left.length && j < right.length) {
if (left[i] <= right[j]) {
result.push(left[i]);
i++;
} else {
result.push(right[j]);
j++;
}
}
return result.concat(left.slice(i)).concat(right.slice(j));
}
}
class Sorter {
constructor() {
this.strategy = null;
}
setStrategy(strategy) {
this.strategy = strategy;
}
sort(array) {
if (!this.strategy) {
throw new Error('Sorting strategy not set');
}
const start = Date.now();
const result = this.strategy.sort(array);
const end = Date.now();
console.log(`Sorting completed in ${end - start}ms`);
return result;
}
}
# Usage
const sorter = new Sorter();
const data = [64, 34, 25, 12, 22, 11, 90];
sorter.setStrategy(new BubbleSortStrategy());
console.log(sorter.sort(data));
sorter.setStrategy(new QuickSortStrategy());
console.log(sorter.sort(data));
sorter.setStrategy(new MergeSortStrategy());
console.log(sorter.sort(data));
# 3. Validation Strategy
class ValidationStrategy {
validate(data) {
throw new Error('validate method must be implemented');
}
}
class EmailValidationStrategy extends ValidationStrategy {
validate(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return {
isValid: emailRegex.test(email),
error: emailRegex.test(email) ? null : 'Invalid email format'
};
}
}
class PasswordValidationStrategy extends ValidationStrategy {
validate(password) {
const errors = [];
if (password.length < 8) {
errors.push('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain at least one uppercase letter');
}
if (!/[a-z]/.test(password)) {
errors.push('Password must contain at least one lowercase letter');
}
if (!/\d/.test(password)) {
errors.push('Password must contain at least one number');
}
if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
errors.push('Password must contain at least one special character');
}
return {
isValid: errors.length === 0,
errors: errors
};
}
}
class PhoneValidationStrategy extends ValidationStrategy {
validate(phone) {
const phoneRegex = /^\+?[\d\s\-\(\)]+$/;
const isValid = phoneRegex.test(phone) && phone.replace(/\D/g, '').length >= 10;
return {
isValid: isValid,
error: isValid ? null : 'Invalid phone number format'
};
}
}
class Validator {
constructor() {
this.strategies = {};
}
addStrategy(field, strategy) {
this.strategies[field] = strategy;
}
validate(data) {
const results = {};
const errors = {};
for (const field in this.strategies) {
const strategy = this.strategies[field];
const result = strategy.validate(data[field]);
results[field] = result.isValid;
if (!result.isValid) {
errors[field] = result.error || result.errors;
}
}
return {
isValid: Object.values(results).every(Boolean),
results: results,
errors: errors
};
}
}
# Usage
const validator = new Validator();
validator.addStrategy('email', new EmailValidationStrategy());
validator.addStrategy('password', new PasswordValidationStrategy());
validator.addStrategy('phone', new PhoneValidationStrategy());
const userData = {
email: 'user@example.com',
password: 'Password123!',
phone: '+1-555-123-4567'
};
const validationResult = validator.validate(userData);
console.log(validationResult);
Design Pattern Selection Guide
Pattern Categories
Creational Patterns
- Singleton - Single instance
- Factory - Object creation
- Builder - Complex object construction
- Prototype - Object cloning
- Abstract Factory - Related object families
Structural Patterns
- Adapter - Interface compatibility
- Decorator - Dynamic functionality
- Facade - Simplified interface
- Proxy - Access control
- Composite - Tree structures
Behavioral Patterns
- Observer - Event handling
- Strategy - Algorithm selection
- Command - Request encapsulation
- State - Object state management
- Template Method - Algorithm skeleton
Pattern Selection
- Identify the problem
- Consider alternatives
- Evaluate trade-offs
- Choose appropriate pattern
- Implement carefully
- Test thoroughly
Summary
Design pattern implementation involves several key areas:
- Creational Patterns: Singleton, Factory, Builder for object creation
- Structural Patterns: Adapter, Decorator, Facade for object composition
- Behavioral Patterns: Observer, Strategy, Command for object interaction
- Pattern Selection: Choose patterns based on specific problems and requirements
Need More Help?
Struggling with design pattern implementation or need help choosing the right pattern? Our design pattern experts can help you implement effective software design strategies.
Get Design Pattern Help