Advanced JavaScript Patterns and Design Principles
60 minAdvanced JavaScript patterns include the Module Pattern, Revealing Module Pattern, Singleton Pattern, and Factory Pattern.
Understanding design principles like SOLID, DRY, and KISS helps create maintainable and scalable JavaScript applications.
The Observer Pattern, Publisher-Subscriber Pattern, and Command Pattern are essential for building event-driven architectures.
Implementing these patterns correctly requires deep understanding of JavaScript's prototypal inheritance and closure mechanisms.
Exercise
Implement a comprehensive system using multiple advanced JavaScript patterns including Observer, Factory, Command, and Strategy patterns with proper error handling and testing.
// Advanced JavaScript Patterns Implementation
import { EventEmitter } from 'events';
// Observer Pattern Implementation
class EventBus extends EventEmitter {
constructor() {
super();
this.subscribers = new Map();
this.eventHistory = [];
}
subscribe(event, callback, options = {}) {
const { once = false, priority = 0 } = options;
if (!this.subscribers.has(event)) {
this.subscribers.set(event, []);
}
const subscriber = { callback, once, priority, id: Date.now() };
this.subscribers.get(event).push(subscriber);
// Sort by priority (higher priority first)
this.subscribers.get(event).sort((a, b) => b.priority - a.priority);
return subscriber.id;
}
unsubscribe(event, subscriberId) {
if (this.subscribers.has(event)) {
const subscribers = this.subscribers.get(event);
const index = subscribers.findIndex(sub => sub.id === subscriberId);
if (index !== -1) {
subscribers.splice(index, 1);
return true;
}
}
return false;
}
publish(event, data) {
this.eventHistory.push({ event, data, timestamp: Date.now() });
if (this.subscribers.has(event)) {
const subscribers = [...this.subscribers.get(event)];
subscribers.forEach(subscriber => {
try {
subscriber.callback(data);
if (subscriber.once) {
this.unsubscribe(event, subscriber.id);
}
} catch (error) {
console.error(`Error in event subscriber for ${event}:`, error);
this.emit('subscriberError', { event, error, subscriber });
}
});
}
this.emit(event, data);
}
getEventHistory(event = null) {
if (event) {
return this.eventHistory.filter(record => record.event === event);
}
return this.eventHistory;
}
clearHistory() {
this.eventHistory = [];
}
}
// Factory Pattern Implementation
class ComponentFactory {
static createComponent(type, config) {
switch (type) {
case 'button':
return new ButtonComponent(config);
case 'input':
return new InputComponent(config);
case 'card':
return new CardComponent(config);
case 'modal':
return new ModalComponent(config);
default:
throw new Error(`Unknown component type: ${type}`);
}
}
static registerComponent(type, ComponentClass) {
ComponentFactory.prototype[type] = ComponentClass;
}
}
// Base Component Class
class BaseComponent {
constructor(config = {}) {
this.id = config.id || `component_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
this.config = { ...this.getDefaultConfig(), ...config };
this.state = {};
this.isMounted = false;
}
getDefaultConfig() {
return {
className: '',
style: {},
attributes: {},
events: {}
};
}
mount(container) {
if (this.isMounted) {
throw new Error('Component is already mounted');
}
this.container = container;
this.render();
this.bindEvents();
this.isMounted = true;
return this;
}
unmount() {
if (!this.isMounted) {
return;
}
this.unbindEvents();
if (this.element) {
this.element.remove();
}
this.isMounted = false;
}
render() {
throw new Error('render method must be implemented by subclass');
}
bindEvents() {
if (this.element && this.config.events) {
Object.entries(this.config.events).forEach(([event, handler]) => {
this.element.addEventListener(event, handler.bind(this));
});
}
}
unbindEvents() {
if (this.element && this.config.events) {
Object.entries(this.config.events).forEach(([event, handler]) => {
this.element.removeEventListener(event, handler.bind(this));
});
}
}
setState(newState) {
this.state = { ...this.state, ...newState };
this.update();
}
update() {
if (this.isMounted) {
this.render();
}
}
}
// Concrete Component Implementations
class ButtonComponent extends BaseComponent {
render() {
this.element = document.createElement('button');
this.element.id = this.id;
this.element.className = this.config.className;
this.element.textContent = this.config.text || 'Button';
Object.entries(this.config.attributes).forEach(([key, value]) => {
this.element.setAttribute(key, value);
});
if (this.container) {
this.container.appendChild(this.element);
}
}
}
class InputComponent extends BaseComponent {
render() {
this.element = document.createElement('input');
this.element.id = this.id;
this.element.className = this.config.className;
this.element.type = this.config.type || 'text';
this.element.placeholder = this.config.placeholder || '';
this.element.value = this.config.value || '';
Object.entries(this.config.attributes).forEach(([key, value]) => {
this.element.setAttribute(key, value);
});
if (this.container) {
this.container.appendChild(this.element);
}
}
}
class CardComponent extends BaseComponent {
render() {
this.element = document.createElement('div');
this.element.id = this.id;
this.element.className = `card ${this.config.className}`;
this.element.innerHTML = `
<div class="card-header">
<h3>${this.config.title || 'Card Title'}</h3>
</div>
<div class="card-body">
<p>${this.config.content || 'Card content goes here'}</p>
</div>
<div class="card-footer">
${this.config.footer || ''}
</div>
`;
if (this.container) {
this.container.appendChild(this.element);
}
}
}
class ModalComponent extends BaseComponent {
render() {
this.element = document.createElement('div');
this.element.id = this.id;
this.element.className = `modal ${this.config.className}`;
this.element.style.display = 'none';
this.element.innerHTML = `
<div class="modal-overlay"></div>
<div class="modal-content">
<div class="modal-header">
<h2>${this.config.title || 'Modal'}</h2>
<button class="modal-close">×</button>
</div>
<div class="modal-body">
${this.config.content || 'Modal content goes here'}
</div>
<div class="modal-footer">
${this.config.footer || ''}
</div>
</div>
`;
if (this.container) {
this.container.appendChild(this.element);
}
}
show() {
if (this.element) {
this.element.style.display = 'block';
}
}
hide() {
if (this.element) {
this.element.style.display = 'none';
}
}
}
// Command Pattern Implementation
class Command {
constructor(execute, undo, name) {
this.execute = execute;
this.undo = undo;
this.name = name;
}
}
class CommandManager {
constructor() {
this.commands = [];
this.currentIndex = -1;
this.maxCommands = 100;
}
execute(command) {
// Remove any commands after current index (for new commands)
this.commands = this.commands.slice(0, this.currentIndex + 1);
// Add new command
this.commands.push(command);
this.currentIndex++;
// Limit command history
if (this.commands.length > this.maxCommands) {
this.commands.shift();
this.currentIndex--;
}
// Execute command
command.execute();
return command;
}
undo() {
if (this.currentIndex >= 0) {
const command = this.commands[this.currentIndex];
command.undo();
this.currentIndex--;
return command;
}
return null;
}
redo() {
if (this.currentIndex < this.commands.length - 1) {
this.currentIndex++;
const command = this.commands[this.currentIndex];
command.execute();
return command;
}
return null;
}
canUndo() {
return this.currentIndex >= 0;
}
canRedo() {
return this.currentIndex < this.commands.length - 1;
}
clear() {
this.commands = [];
this.currentIndex = -1;
}
}
// Strategy Pattern Implementation
class ValidationStrategy {
validate(value) {
throw new Error('validate method must be implemented by subclass');
}
}
class RequiredValidationStrategy extends ValidationStrategy {
validate(value) {
return value !== null && value !== undefined && value !== '';
}
}
class EmailValidationStrategy extends ValidationStrategy {
validate(value) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(value);
}
}
class MinLengthValidationStrategy extends ValidationStrategy {
constructor(minLength) {
super();
this.minLength = minLength;
}
validate(value) {
return String(value).length >= this.minLength;
}
}
class MaxLengthValidationStrategy extends ValidationStrategy {
constructor(maxLength) {
super();
this.maxLength = maxLength;
}
validate(value) {
return String(value).length <= this.maxLength;
}
}
class Validator {
constructor() {
this.strategies = new Map();
this.registerDefaultStrategies();
}
registerDefaultStrategies() {
this.registerStrategy('required', new RequiredValidationStrategy());
this.registerStrategy('email', new EmailValidationStrategy());
this.registerStrategy('minLength', new MinLengthValidationStrategy(3));
this.registerStrategy('maxLength', new MaxLengthValidationStrategy(100));
}
registerStrategy(name, strategy) {
this.strategies.set(name, strategy);
}
validate(value, strategyNames) {
const errors = [];
strategyNames.forEach(strategyName => {
const strategy = this.strategies.get(strategyName);
if (strategy) {
if (!strategy.validate(value)) {
errors.push(`Validation failed for strategy: ${strategyName}`);
}
} else {
errors.push(`Unknown validation strategy: ${strategyName}`);
}
});
return {
isValid: errors.length === 0,
errors
};
}
}
// Usage Example and Demonstration
class PatternDemo {
constructor() {
this.eventBus = new EventBus();
this.componentFactory = ComponentFactory;
this.commandManager = new CommandManager();
this.validator = new Validator();
this.container = null;
}
initialize(containerId) {
this.container = document.getElementById(containerId);
if (!this.container) {
throw new Error(`Container with id '${containerId}' not found`);
}
this.setupEventListeners();
this.createComponents();
this.setupValidation();
console.log('Pattern Demo initialized successfully');
}
setupEventListeners() {
// Subscribe to component events
this.eventBus.subscribe('componentCreated', (data) => {
console.log('Component created:', data);
}, { priority: 1 });
this.eventBus.subscribe('componentMounted', (data) => {
console.log('Component mounted:', data);
});
this.eventBus.subscribe('validationError', (data) => {
console.error('Validation error:', data);
});
}
createComponents() {
// Create components using factory
const button = this.componentFactory.createComponent('button', {
text: 'Click Me',
className: 'btn btn-primary',
events: {
click: () => this.handleButtonClick()
}
});
const input = this.componentFactory.createComponent('input', {
type: 'text',
placeholder: 'Enter your name',
className: 'form-control',
events: {
input: (e) => this.handleInputChange(e)
}
});
const card = this.componentFactory.createComponent('card', {
title: 'Pattern Demo Card',
content: 'This card demonstrates the Factory Pattern in action.',
footer: '<button class="btn btn-sm btn-secondary">Action</button>'
});
// Mount components
button.mount(this.container);
input.mount(this.container);
card.mount(this.container);
// Publish events
this.eventBus.publish('componentCreated', { type: 'button', id: button.id });
this.eventBus.publish('componentMounted', { type: 'button', id: button.id });
}
setupValidation() {
const emailInput = this.componentFactory.createComponent('input', {
type: 'email',
placeholder: 'Enter your email',
className: 'form-control',
events: {
blur: (e) => this.validateEmail(e.target.value)
}
});
emailInput.mount(this.container);
}
handleButtonClick() {
// Create a command for the button click
const command = new Command(
() => {
console.log('Button clicked!');
this.container.style.backgroundColor = this.getRandomColor();
},
() => {
this.container.style.backgroundColor = '';
},
'Button Click'
);
this.commandManager.execute(command);
}
handleInputChange(event) {
const value = event.target.value;
console.log('Input changed:', value);
}
validateEmail(email) {
const result = this.validator.validate(email, ['required', 'email']);
if (!result.isValid) {
this.eventBus.publish('validationError', {
field: 'email',
value: email,
errors: result.errors
});
}
}
getRandomColor() {
const colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57'];
return colors[Math.floor(Math.random() * colors.length)];
}
undo() {
if (this.commandManager.canUndo()) {
this.commandManager.undo();
console.log('Undo executed');
}
}
redo() {
if (this.commandManager.canRedo()) {
this.commandManager.redo();
console.log('Redo executed');
}
}
showCommandHistory() {
console.log('Command History:');
this.commandManager.commands.forEach((command, index) => {
const marker = index === this.commandManager.currentIndex ? ' > ' : ' ';
console.log(`${marker}${command.name}`);
});
}
}
// Export for use in other modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
EventBus,
ComponentFactory,
CommandManager,
Validator,
PatternDemo
};
} else if (typeof window !== 'undefined') {
window.PatternDemo = PatternDemo;
window.EventBus = EventBus;
window.ComponentFactory = ComponentFactory;
window.CommandManager = CommandManager;
window.Validator = Validator;
}
// Usage example
function demonstratePatterns() {
console.log('=== Advanced JavaScript Patterns Demo ===\n');
try {
const demo = new PatternDemo();
demo.initialize('pattern-demo-container');
// Add undo/redo buttons
const controls = document.createElement('div');
controls.innerHTML = `
<button onclick="demo.undo()">Undo</button>
<button onclick="demo.redo()">Redo</button>
<button onclick="demo.showCommandHistory()">Show History</button>
`;
demo.container.appendChild(controls);
console.log('Pattern demo is ready! Click the button to see commands in action.');
} catch (error) {
console.error('Failed to initialize pattern demo:', error);
}
}
// Auto-run if in browser
if (typeof window !== 'undefined') {
window.demonstratePatterns = demonstratePatterns;
// Create demo container if it doesn't exist
if (!document.getElementById('pattern-demo-container')) {
const container = document.createElement('div');
container.id = 'pattern-demo-container';
container.style.cssText = 'padding: 20px; border: 1px solid #ccc; margin: 20px;';
document.body.appendChild(container);
}
}