javascript
examples
examples.jsâĄjavascript
/**
* ========================================
* 8.5 CLASS PATTERNS - CODE EXAMPLES
* ========================================
*/
/**
* EXAMPLE 1: Factory Pattern
* Creating objects without specifying exact class
*/
console.log('--- Example 1: Factory Pattern ---');
// Product classes
class Car {
constructor(options) {
this.type = 'car';
this.wheels = 4;
this.make = options.make;
this.model = options.model;
}
describe() {
return `${this.make} ${this.model} (${this.type})`;
}
}
class Motorcycle {
constructor(options) {
this.type = 'motorcycle';
this.wheels = 2;
this.make = options.make;
this.model = options.model;
}
describe() {
return `${this.make} ${this.model} (${this.type})`;
}
}
class Truck {
constructor(options) {
this.type = 'truck';
this.wheels = 6;
this.make = options.make;
this.model = options.model;
this.payload = options.payload || 0;
}
describe() {
return `${this.make} ${this.model} (${this.type}, payload: ${this.payload}kg)`;
}
}
// Factory
class VehicleFactory {
static create(type, options) {
switch (type.toLowerCase()) {
case 'car':
return new Car(options);
case 'motorcycle':
return new Motorcycle(options);
case 'truck':
return new Truck(options);
default:
throw new Error(`Unknown vehicle type: ${type}`);
}
}
}
const car = VehicleFactory.create('car', { make: 'Toyota', model: 'Camry' });
const bike = VehicleFactory.create('motorcycle', {
make: 'Honda',
model: 'CBR',
});
const truck = VehicleFactory.create('truck', {
make: 'Ford',
model: 'F-150',
payload: 1000,
});
console.log(car.describe());
console.log(bike.describe());
console.log(truck.describe());
/**
* EXAMPLE 2: Abstract Factory Pattern
* Factory for creating related objects
*/
console.log('\n--- Example 2: Abstract Factory ---');
// Abstract products
class Button {
render() {
throw new Error('Must implement render()');
}
}
class Input {
render() {
throw new Error('Must implement render()');
}
}
// Concrete products - Light theme
class LightButton extends Button {
render() {
return '<button class="btn-light">Click</button>';
}
}
class LightInput extends Input {
render() {
return '<input class="input-light" />';
}
}
// Concrete products - Dark theme
class DarkButton extends Button {
render() {
return '<button class="btn-dark">Click</button>';
}
}
class DarkInput extends Input {
render() {
return '<input class="input-dark" />';
}
}
// Abstract factory
class UIFactory {
createButton() {
throw new Error('Must implement createButton()');
}
createInput() {
throw new Error('Must implement createInput()');
}
}
// Concrete factories
class LightUIFactory extends UIFactory {
createButton() {
return new LightButton();
}
createInput() {
return new LightInput();
}
}
class DarkUIFactory extends UIFactory {
createButton() {
return new DarkButton();
}
createInput() {
return new DarkInput();
}
}
// Usage
function createUI(factory) {
const button = factory.createButton();
const input = factory.createInput();
return { button, input };
}
const lightUI = createUI(new LightUIFactory());
const darkUI = createUI(new DarkUIFactory());
console.log('Light UI:', lightUI.button.render());
console.log('Dark UI:', darkUI.button.render());
/**
* EXAMPLE 3: Builder Pattern
* Step-by-step object construction
*/
console.log('\n--- Example 3: Builder Pattern ---');
class Pizza {
constructor() {
this.size = 'medium';
this.crust = 'regular';
this.toppings = [];
this.sauce = 'tomato';
}
describe() {
return (
`${this.size} pizza with ${this.crust} crust, ` +
`${this.sauce} sauce, and ${this.toppings.join(', ') || 'no toppings'}`
);
}
}
class PizzaBuilder {
constructor() {
this.pizza = new Pizza();
}
setSize(size) {
this.pizza.size = size;
return this;
}
setCrust(crust) {
this.pizza.crust = crust;
return this;
}
setSauce(sauce) {
this.pizza.sauce = sauce;
return this;
}
addTopping(topping) {
this.pizza.toppings.push(topping);
return this;
}
build() {
return this.pizza;
}
}
// Director for common configurations
class PizzaDirector {
static createMargherita(builder) {
return builder
.setSize('medium')
.setCrust('thin')
.setSauce('tomato')
.addTopping('mozzarella')
.addTopping('basil')
.build();
}
static createMeatLovers(builder) {
return builder
.setSize('large')
.setCrust('thick')
.setSauce('tomato')
.addTopping('pepperoni')
.addTopping('sausage')
.addTopping('bacon')
.addTopping('ham')
.build();
}
}
const customPizza = new PizzaBuilder()
.setSize('large')
.setCrust('stuffed')
.addTopping('cheese')
.addTopping('mushrooms')
.build();
console.log(customPizza.describe());
const margherita = PizzaDirector.createMargherita(new PizzaBuilder());
console.log(margherita.describe());
/**
* EXAMPLE 4: Advanced Builder - Query Builder
*/
console.log('\n--- Example 4: Query Builder ---');
class QueryBuilder {
#table = '';
#columns = ['*'];
#conditions = [];
#joins = [];
#orderBy = [];
#limit = null;
#offset = null;
select(...columns) {
this.#columns = columns.length ? columns : ['*'];
return this;
}
from(table) {
this.#table = table;
return this;
}
where(condition) {
this.#conditions.push({ type: 'AND', condition });
return this;
}
orWhere(condition) {
this.#conditions.push({ type: 'OR', condition });
return this;
}
join(table, condition, type = 'INNER') {
this.#joins.push(`${type} JOIN ${table} ON ${condition}`);
return this;
}
leftJoin(table, condition) {
return this.join(table, condition, 'LEFT');
}
orderBy(column, direction = 'ASC') {
this.#orderBy.push(`${column} ${direction}`);
return this;
}
limit(n) {
this.#limit = n;
return this;
}
offset(n) {
this.#offset = n;
return this;
}
build() {
let query = `SELECT ${this.#columns.join(', ')} FROM ${this.#table}`;
if (this.#joins.length) {
query += ` ${this.#joins.join(' ')}`;
}
if (this.#conditions.length) {
const whereClause = this.#conditions
.map((c, i) => (i === 0 ? c.condition : `${c.type} ${c.condition}`))
.join(' ');
query += ` WHERE ${whereClause}`;
}
if (this.#orderBy.length) {
query += ` ORDER BY ${this.#orderBy.join(', ')}`;
}
if (this.#limit !== null) {
query += ` LIMIT ${this.#limit}`;
}
if (this.#offset !== null) {
query += ` OFFSET ${this.#offset}`;
}
return query;
}
}
const query = new QueryBuilder()
.select('users.name', 'orders.total')
.from('users')
.leftJoin('orders', 'users.id = orders.user_id')
.where('users.active = true')
.where('orders.total > 100')
.orderBy('orders.total', 'DESC')
.limit(10)
.build();
console.log(query);
/**
* EXAMPLE 5: Singleton Pattern
* Single instance throughout application
*/
console.log('\n--- Example 5: Singleton Pattern ---');
class Logger {
static #instance = null;
#logs = [];
constructor() {
if (Logger.#instance) {
return Logger.#instance;
}
Logger.#instance = this;
}
static getInstance() {
if (!Logger.#instance) {
new Logger();
}
return Logger.#instance;
}
log(level, message) {
const entry = {
level,
message,
timestamp: new Date().toISOString(),
};
this.#logs.push(entry);
console.log(`[${level}] ${entry.timestamp}: ${message}`);
}
info(message) {
this.log('INFO', message);
}
warn(message) {
this.log('WARN', message);
}
error(message) {
this.log('ERROR', message);
}
getLogs() {
return [...this.#logs];
}
}
const logger1 = Logger.getInstance();
const logger2 = Logger.getInstance();
console.log('Same instance:', logger1 === logger2);
logger1.info('Application started');
logger2.warn('Low memory');
/**
* EXAMPLE 6: Mixin Pattern
* Composable behaviors
*/
console.log('\n--- Example 6: Mixin Pattern ---');
// Mixin factories
const Timestamped = (Base) =>
class extends Base {
constructor(...args) {
super(...args);
this.createdAt = new Date();
this.updatedAt = new Date();
}
touch() {
this.updatedAt = new Date();
return this;
}
};
const Identifiable = (Base) =>
class extends Base {
static #nextId = 1;
constructor(...args) {
super(...args);
this.id = Identifiable.#nextId++;
}
};
const Serializable = (Base) =>
class extends Base {
toJSON() {
return { ...this };
}
toString() {
return JSON.stringify(this.toJSON());
}
static fromJSON(json) {
const data = typeof json === 'string' ? JSON.parse(json) : json;
return Object.assign(new this(), data);
}
};
const Validatable = (Base) =>
class extends Base {
#rules = new Map();
addRule(field, validator) {
if (!this.#rules.has(field)) {
this.#rules.set(field, []);
}
this.#rules.get(field).push(validator);
return this;
}
validate() {
const errors = [];
for (const [field, validators] of this.#rules) {
for (const validator of validators) {
const result = validator(this[field]);
if (result !== true) {
errors.push({ field, error: result });
}
}
}
return { valid: errors.length === 0, errors };
}
};
// Base class
class Entity {
constructor() {}
}
// Compose mixins
class User extends Serializable(
Validatable(Identifiable(Timestamped(Entity)))
) {
constructor(name, email) {
super();
this.name = name;
this.email = email;
}
}
const user = new User('Alice', 'alice@example.com');
user.addRule('email', (v) => v.includes('@') || 'Invalid email');
user.addRule('name', (v) => v.length >= 2 || 'Name too short');
console.log('User:', user.toJSON());
console.log('Validation:', user.validate());
/**
* EXAMPLE 7: Observer Pattern
* Event-driven communication
*/
console.log('\n--- Example 7: Observer Pattern ---');
class EventEmitter {
#events = new Map();
on(event, callback) {
if (!this.#events.has(event)) {
this.#events.set(event, new Set());
}
this.#events.get(event).add(callback);
return () => this.off(event, callback);
}
off(event, callback) {
const callbacks = this.#events.get(event);
if (callbacks) {
callbacks.delete(callback);
}
}
emit(event, ...args) {
const callbacks = this.#events.get(event);
if (callbacks) {
for (const callback of callbacks) {
callback(...args);
}
}
}
once(event, callback) {
const wrapper = (...args) => {
this.off(event, wrapper);
callback(...args);
};
return this.on(event, wrapper);
}
}
class Store extends EventEmitter {
#state = {};
getState() {
return { ...this.#state };
}
setState(newState) {
const oldState = this.#state;
this.#state = { ...this.#state, ...newState };
this.emit('change', this.#state, oldState);
}
}
const store = new Store();
const unsubscribe = store.on('change', (newState, oldState) => {
console.log('State changed:', oldState, 'â', newState);
});
store.setState({ count: 1 });
store.setState({ count: 2, name: 'test' });
unsubscribe();
store.setState({ count: 3 }); // No log
/**
* EXAMPLE 8: Strategy Pattern
* Interchangeable algorithms
*/
console.log('\n--- Example 8: Strategy Pattern ---');
// Strategy interface
class SortStrategy {
sort(arr) {
throw new Error('Must implement sort()');
}
}
// Concrete strategies
class BubbleSort extends SortStrategy {
sort(arr) {
const result = [...arr];
for (let i = 0; i < result.length; i++) {
for (let j = 0; j < result.length - i - 1; j++) {
if (result[j] > result[j + 1]) {
[result[j], result[j + 1]] = [result[j + 1], result[j]];
}
}
}
console.log('Using Bubble Sort');
return result;
}
}
class QuickSort extends SortStrategy {
sort(arr) {
console.log('Using Quick Sort');
if (arr.length <= 1) return arr;
const pivot = arr[Math.floor(arr.length / 2)];
const left = arr.filter((x) => x < pivot);
const middle = arr.filter((x) => x === pivot);
const right = arr.filter((x) => x > pivot);
return [...this.sort(left), ...middle, ...this.sort(right)];
}
}
class MergeSort extends SortStrategy {
sort(arr) {
console.log('Using Merge Sort');
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 = [];
while (left.length && right.length) {
result.push(left[0] < right[0] ? left.shift() : right.shift());
}
return [...result, ...left, ...right];
}
}
// Context
class Sorter {
#strategy;
setStrategy(strategy) {
this.#strategy = strategy;
return this;
}
sort(arr) {
if (!this.#strategy) {
throw new Error('No sorting strategy set');
}
return this.#strategy.sort(arr);
}
}
const data = [5, 2, 8, 1, 9];
const sorter = new Sorter();
console.log(sorter.setStrategy(new BubbleSort()).sort(data));
console.log(sorter.setStrategy(new QuickSort()).sort(data));
console.log(sorter.setStrategy(new MergeSort()).sort(data));
/**
* EXAMPLE 9: Decorator Pattern
* Add behavior dynamically
*/
console.log('\n--- Example 9: Decorator Pattern ---');
// Base component
class Coffee {
cost() {
return 5;
}
description() {
return 'Coffee';
}
}
// Base decorator
class CoffeeDecorator {
constructor(coffee) {
this.coffee = coffee;
}
cost() {
return this.coffee.cost();
}
description() {
return this.coffee.description();
}
}
// Concrete decorators
class MilkDecorator extends CoffeeDecorator {
cost() {
return super.cost() + 2;
}
description() {
return `${super.description()} + Milk`;
}
}
class SugarDecorator extends CoffeeDecorator {
cost() {
return super.cost() + 0.5;
}
description() {
return `${super.description()} + Sugar`;
}
}
class WhipDecorator extends CoffeeDecorator {
cost() {
return super.cost() + 1.5;
}
description() {
return `${super.description()} + Whipped Cream`;
}
}
class SizeDecorator extends CoffeeDecorator {
constructor(coffee, size = 'medium') {
super(coffee);
this.size = size;
this.multipliers = { small: 0.8, medium: 1, large: 1.5 };
}
cost() {
return super.cost() * this.multipliers[this.size];
}
description() {
return `${
this.size.charAt(0).toUpperCase() + this.size.slice(1)
} ${super.description()}`;
}
}
// Build a drink
let order = new Coffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
order = new WhipDecorator(order);
order = new SizeDecorator(order, 'large');
console.log(order.description()); // "Large Coffee + Milk + Sugar + Whipped Cream"
console.log(`Total: $${order.cost().toFixed(2)}`); // Total: $13.50
/**
* EXAMPLE 10: Command Pattern
* Encapsulate actions for undo/redo
*/
console.log('\n--- Example 10: Command Pattern ---');
// Command interface
class Command {
execute() {
throw new Error('Must implement execute()');
}
undo() {
throw new Error('Must implement undo()');
}
}
// Receiver
class TextEditor {
#content = '';
getContent() {
return this.#content;
}
insertText(text, position) {
const before = this.#content.slice(0, position);
const after = this.#content.slice(position);
this.#content = before + text + after;
}
deleteText(start, length) {
const deleted = this.#content.slice(start, start + length);
this.#content =
this.#content.slice(0, start) + this.#content.slice(start + length);
return deleted;
}
}
// Concrete commands
class InsertCommand extends Command {
#editor;
#text;
#position;
constructor(editor, text, position) {
super();
this.#editor = editor;
this.#text = text;
this.#position = position;
}
execute() {
this.#editor.insertText(this.#text, this.#position);
}
undo() {
this.#editor.deleteText(this.#position, this.#text.length);
}
}
class DeleteCommand extends Command {
#editor;
#start;
#length;
#deletedText;
constructor(editor, start, length) {
super();
this.#editor = editor;
this.#start = start;
this.#length = length;
}
execute() {
this.#deletedText = this.#editor.deleteText(this.#start, this.#length);
}
undo() {
this.#editor.insertText(this.#deletedText, this.#start);
}
}
// Invoker
class CommandManager {
#history = [];
#redoStack = [];
execute(command) {
command.execute();
this.#history.push(command);
this.#redoStack = [];
}
undo() {
const command = this.#history.pop();
if (command) {
command.undo();
this.#redoStack.push(command);
}
}
redo() {
const command = this.#redoStack.pop();
if (command) {
command.execute();
this.#history.push(command);
}
}
}
const editor = new TextEditor();
const commandManager = new CommandManager();
commandManager.execute(new InsertCommand(editor, 'Hello', 0));
console.log(editor.getContent()); // "Hello"
commandManager.execute(new InsertCommand(editor, ' World', 5));
console.log(editor.getContent()); // "Hello World"
commandManager.undo();
console.log(editor.getContent()); // "Hello"
commandManager.redo();
console.log(editor.getContent()); // "Hello World"
/**
* EXAMPLE 11: Repository Pattern
* Abstract data access layer
*/
console.log('\n--- Example 11: Repository Pattern ---');
// Repository interface
class Repository {
findById(id) {
throw new Error('Not implemented');
}
findAll() {
throw new Error('Not implemented');
}
save(entity) {
throw new Error('Not implemented');
}
delete(id) {
throw new Error('Not implemented');
}
exists(id) {
return this.findById(id) !== null;
}
count() {
return this.findAll().length;
}
}
// In-memory implementation
class InMemoryRepository extends Repository {
#store = new Map();
findById(id) {
return this.#store.get(id) || null;
}
findAll() {
return [...this.#store.values()];
}
save(entity) {
if (!entity.id) {
entity.id = Date.now();
}
this.#store.set(entity.id, entity);
return entity;
}
delete(id) {
return this.#store.delete(id);
}
}
// Specialized repository
class UserRepository extends InMemoryRepository {
findByEmail(email) {
return this.findAll().find((user) => user.email === email) || null;
}
findActiveUsers() {
return this.findAll().filter((user) => user.active);
}
}
const userRepo = new UserRepository();
userRepo.save({
id: 1,
name: 'Alice',
email: 'alice@example.com',
active: true,
});
userRepo.save({ id: 2, name: 'Bob', email: 'bob@example.com', active: false });
userRepo.save({
id: 3,
name: 'Charlie',
email: 'charlie@example.com',
active: true,
});
console.log(
'All users:',
userRepo.findAll().map((u) => u.name)
);
console.log(
'Active users:',
userRepo.findActiveUsers().map((u) => u.name)
);
console.log('Find by email:', userRepo.findByEmail('bob@example.com'));
/**
* EXAMPLE 12: State Pattern
* Object behavior changes with state
*/
console.log('\n--- Example 12: State Pattern ---');
// State interface
class State {
handle(context) {
throw new Error('Must implement handle()');
}
}
// Concrete states
class DraftState extends State {
handle(context) {
console.log('Document is in Draft state');
}
publish(context) {
console.log('Moving from Draft to Moderation');
context.setState(new ModerationState());
}
}
class ModerationState extends State {
handle(context) {
console.log('Document is under Moderation');
}
approve(context) {
console.log('Moving from Moderation to Published');
context.setState(new PublishedState());
}
reject(context) {
console.log('Moving from Moderation back to Draft');
context.setState(new DraftState());
}
}
class PublishedState extends State {
handle(context) {
console.log('Document is Published');
}
unpublish(context) {
console.log('Moving from Published to Draft');
context.setState(new DraftState());
}
}
// Context
class Document {
#state;
constructor() {
this.#state = new DraftState();
}
setState(state) {
this.#state = state;
}
status() {
this.#state.handle(this);
}
publish() {
if (this.#state.publish) this.#state.publish(this);
}
approve() {
if (this.#state.approve) this.#state.approve(this);
}
reject() {
if (this.#state.reject) this.#state.reject(this);
}
unpublish() {
if (this.#state.unpublish) this.#state.unpublish(this);
}
}
const doc = new Document();
doc.status(); // Draft
doc.publish(); // â Moderation
doc.status(); // Moderation
doc.approve(); // â Published
doc.status(); // Published
/**
* EXAMPLE 13: Adapter Pattern
* Convert interface to another
*/
console.log('\n--- Example 13: Adapter Pattern ---');
// Old API
class OldPaymentSystem {
processPayment(data) {
console.log(`Old system: Processing ${data.currency}${data.amount}`);
return { status: 'ok', transactionId: `OLD-${Date.now()}` };
}
}
// New API expected interface
class PaymentProcessor {
pay(amount, currency) {
throw new Error('Must implement pay()');
}
}
// Adapter
class OldPaymentAdapter extends PaymentProcessor {
#oldSystem;
constructor() {
super();
this.#oldSystem = new OldPaymentSystem();
}
pay(amount, currency) {
// Adapt new interface to old system
const result = this.#oldSystem.processPayment({
amount,
currency,
});
// Adapt old response to new format
return {
success: result.status === 'ok',
id: result.transactionId,
};
}
}
// New system
class NewPaymentSystem extends PaymentProcessor {
pay(amount, currency) {
console.log(`New system: Processing ${currency}${amount}`);
return { success: true, id: `NEW-${Date.now()}` };
}
}
// Client code works with both
function processOrder(processor, amount, currency) {
return processor.pay(amount, currency);
}
const oldAdapter = new OldPaymentAdapter();
const newSystem = new NewPaymentSystem();
console.log(processOrder(oldAdapter, 100, '$'));
console.log(processOrder(newSystem, 100, '$'));
/**
* EXAMPLE 14: Dependency Injection
* Loosely coupled components
*/
console.log('\n--- Example 14: Dependency Injection ---');
// Dependencies (can be mocked for testing)
class EmailService {
send(to, subject, body) {
console.log(`Sending email to ${to}: ${subject}`);
return true;
}
}
class Logger {
log(message) {
console.log(`[LOG] ${message}`);
}
}
class UserRepository {
save(user) {
console.log(`Saving user: ${user.name}`);
return { ...user, id: Date.now() };
}
findById(id) {
return { id, name: 'Test User', email: 'test@example.com' };
}
}
// Service with injected dependencies
class UserService {
#userRepo;
#emailService;
#logger;
constructor(userRepo, emailService, logger) {
this.#userRepo = userRepo;
this.#emailService = emailService;
this.#logger = logger;
}
async createUser(userData) {
this.#logger.log(`Creating user: ${userData.name}`);
const user = this.#userRepo.save(userData);
this.#emailService.send(user.email, 'Welcome!', 'Thanks for signing up.');
this.#logger.log(`User created: ${user.id}`);
return user;
}
}
// DI Container (simple version)
class Container {
#services = new Map();
register(name, factory) {
this.#services.set(name, { factory, instance: null });
}
registerSingleton(name, factory) {
this.#services.set(name, { factory, singleton: true, instance: null });
}
resolve(name) {
const service = this.#services.get(name);
if (!service) throw new Error(`Service not found: ${name}`);
if (service.singleton && service.instance) {
return service.instance;
}
const instance = service.factory(this);
if (service.singleton) {
service.instance = instance;
}
return instance;
}
}
// Setup container
const container = new Container();
container.registerSingleton('logger', () => new Logger());
container.registerSingleton('emailService', () => new EmailService());
container.registerSingleton('userRepo', () => new UserRepository());
container.register(
'userService',
(c) =>
new UserService(
c.resolve('userRepo'),
c.resolve('emailService'),
c.resolve('logger')
)
);
// Usage
const userService = container.resolve('userService');
userService.createUser({ name: 'Alice', email: 'alice@example.com' });
/**
* EXAMPLE 15: Composite Pattern
* Tree structures with uniform interface
*/
console.log('\n--- Example 15: Composite Pattern ---');
// Component interface
class FileSystemItem {
constructor(name) {
this.name = name;
}
getSize() {
throw new Error('Must implement getSize()');
}
print(indent = 0) {
throw new Error('Must implement print()');
}
}
// Leaf
class File extends FileSystemItem {
constructor(name, size) {
super(name);
this.size = size;
}
getSize() {
return this.size;
}
print(indent = 0) {
console.log(`${' '.repeat(indent)}đ ${this.name} (${this.size}KB)`);
}
}
// Composite
class Directory extends FileSystemItem {
#children = [];
constructor(name) {
super(name);
}
add(item) {
this.#children.push(item);
return this;
}
remove(item) {
const index = this.#children.indexOf(item);
if (index > -1) {
this.#children.splice(index, 1);
}
return this;
}
getSize() {
return this.#children.reduce((total, child) => total + child.getSize(), 0);
}
print(indent = 0) {
console.log(`${' '.repeat(indent)}đ ${this.name}/ (${this.getSize()}KB)`);
for (const child of this.#children) {
child.print(indent + 1);
}
}
}
// Build file system
const root = new Directory('root');
const docs = new Directory('documents');
const images = new Directory('images');
docs.add(new File('readme.txt', 5)).add(new File('notes.md', 10));
images.add(new File('photo1.jpg', 500)).add(new File('photo2.jpg', 750));
root.add(docs).add(images).add(new File('config.json', 2));
root.print();
console.log(`Total size: ${root.getSize()}KB`);
/**
* EXAMPLE 16: Prototype Pattern (using classes)
* Clone objects
*/
console.log('\n--- Example 16: Prototype Pattern ---');
class Shape {
constructor(x = 0, y = 0, color = 'black') {
this.x = x;
this.y = y;
this.color = color;
}
clone() {
return Object.assign(Object.create(Object.getPrototypeOf(this)), this);
}
}
class Rectangle extends Shape {
constructor(x, y, width, height, color) {
super(x, y, color);
this.width = width;
this.height = height;
}
area() {
return this.width * this.height;
}
}
class Circle extends Shape {
constructor(x, y, radius, color) {
super(x, y, color);
this.radius = radius;
}
area() {
return Math.PI * this.radius ** 2;
}
}
const original = new Rectangle(10, 20, 100, 50, 'blue');
const cloned = original.clone();
cloned.x = 30;
cloned.color = 'red';
console.log('Original:', original);
console.log('Cloned:', cloned);
console.log('Same type:', cloned instanceof Rectangle);
/**
* EXAMPLE 17: Facade Pattern
* Simplified interface to complex subsystem
*/
console.log('\n--- Example 17: Facade Pattern ---');
// Complex subsystem classes
class CPU {
freeze() {
console.log('CPU: Freezing...');
}
jump(address) {
console.log(`CPU: Jumping to ${address}`);
}
execute() {
console.log('CPU: Executing...');
}
}
class Memory {
load(address, data) {
console.log(`Memory: Loading "${data}" at ${address}`);
}
}
class HardDrive {
read(sector, size) {
console.log(`HardDrive: Reading ${size} bytes from sector ${sector}`);
return 'boot_data';
}
}
// Facade
class ComputerFacade {
#cpu;
#memory;
#hardDrive;
constructor() {
this.#cpu = new CPU();
this.#memory = new Memory();
this.#hardDrive = new HardDrive();
}
start() {
console.log('Computer: Starting...');
this.#cpu.freeze();
const bootData = this.#hardDrive.read(0, 1024);
this.#memory.load(0x00, bootData);
this.#cpu.jump(0x00);
this.#cpu.execute();
console.log('Computer: Started!');
}
}
const computer = new ComputerFacade();
computer.start();
/**
* EXAMPLE 18: Chain of Responsibility
* Pass requests along a chain
*/
console.log('\n--- Example 18: Chain of Responsibility ---');
class Handler {
#next = null;
setNext(handler) {
this.#next = handler;
return handler;
}
handle(request) {
if (this.#next) {
return this.#next.handle(request);
}
return null;
}
}
class AuthHandler extends Handler {
handle(request) {
if (!request.userId) {
return { error: 'Authentication required' };
}
console.log('Auth: User authenticated');
return super.handle(request);
}
}
class ValidationHandler extends Handler {
handle(request) {
if (!request.data || request.data.length === 0) {
return { error: 'Invalid request data' };
}
console.log('Validation: Request data valid');
return super.handle(request);
}
}
class RateLimitHandler extends Handler {
#requests = new Map();
#limit = 3;
handle(request) {
const count = this.#requests.get(request.userId) || 0;
if (count >= this.#limit) {
return { error: 'Rate limit exceeded' };
}
this.#requests.set(request.userId, count + 1);
console.log('RateLimit: Request allowed');
return super.handle(request);
}
}
class RequestHandler extends Handler {
handle(request) {
console.log('Handler: Processing request');
return { success: true, data: `Processed: ${request.data}` };
}
}
// Build chain
const auth = new AuthHandler();
const validate = new ValidationHandler();
const rateLimit = new RateLimitHandler();
const requestHandler = new RequestHandler();
auth.setNext(validate).setNext(rateLimit).setNext(requestHandler);
console.log('\nValid request:');
console.log(auth.handle({ userId: 'user1', data: 'test data' }));
console.log('\nNo auth:');
console.log(auth.handle({ data: 'test data' }));
console.log('\nNo data:');
console.log(auth.handle({ userId: 'user1' }));
/**
* EXAMPLE 19: Mediator Pattern
* Centralized communication
*/
console.log('\n--- Example 19: Mediator Pattern ---');
class ChatRoom {
#users = new Map();
register(user) {
this.#users.set(user.name, user);
user.chatRoom = this;
}
send(message, from, to) {
if (to) {
// Direct message
const user = this.#users.get(to);
if (user) {
user.receive(message, from);
}
} else {
// Broadcast
for (const [name, user] of this.#users) {
if (name !== from) {
user.receive(message, from);
}
}
}
}
}
class ChatUser {
constructor(name) {
this.name = name;
this.chatRoom = null;
}
send(message, to) {
console.log(
`${this.name} sends: ${message}${to ? ` to ${to}` : ' (broadcast)'}`
);
this.chatRoom.send(message, this.name, to);
}
receive(message, from) {
console.log(`${this.name} received from ${from}: ${message}`);
}
}
const chatRoom = new ChatRoom();
const alice = new ChatUser('Alice');
const bob = new ChatUser('Bob');
const charlie = new ChatUser('Charlie');
chatRoom.register(alice);
chatRoom.register(bob);
chatRoom.register(charlie);
alice.send('Hello everyone!');
bob.send('Hi Alice!', 'Alice');
/**
* EXAMPLE 20: Complete Application Example
* Combining multiple patterns
*/
console.log('\n--- Example 20: Complete Application ---');
// Mixin for timestamps
const Timestamped = (Base) =>
class extends Base {
constructor(...args) {
super(...args);
this.createdAt = new Date();
}
};
// Entity base class
class Entity {
constructor() {}
}
// Task model
class Task extends Timestamped(Entity) {
constructor(title, description) {
super();
this.id = Date.now();
this.title = title;
this.description = description;
this.status = 'pending';
}
complete() {
this.status = 'completed';
}
}
// Repository
class TaskRepository {
#tasks = new Map();
save(task) {
this.#tasks.set(task.id, task);
return task;
}
findById(id) {
return this.#tasks.get(id);
}
findAll() {
return [...this.#tasks.values()];
}
findByStatus(status) {
return this.findAll().filter((t) => t.status === status);
}
}
// Command for undo/redo
class CompleteTaskCommand {
constructor(task) {
this.task = task;
this.previousStatus = null;
}
execute() {
this.previousStatus = this.task.status;
this.task.complete();
}
undo() {
this.task.status = this.previousStatus;
}
}
// Observer for notifications
class TaskEventEmitter extends EventEmitter {}
// Service combining patterns
class TaskService {
#repo;
#events;
#commandHistory = [];
constructor(repo, events) {
this.#repo = repo;
this.#events = events;
}
createTask(title, description) {
const task = new Task(title, description);
this.#repo.save(task);
this.#events.emit('task:created', task);
return task;
}
completeTask(taskId) {
const task = this.#repo.findById(taskId);
if (!task) throw new Error('Task not found');
const command = new CompleteTaskCommand(task);
command.execute();
this.#commandHistory.push(command);
this.#events.emit('task:completed', task);
return task;
}
undoLastAction() {
const command = this.#commandHistory.pop();
if (command) {
command.undo();
this.#events.emit('task:undone', command.task);
}
}
getAllTasks() {
return this.#repo.findAll();
}
getPendingTasks() {
return this.#repo.findByStatus('pending');
}
}
// Usage
const taskRepo = new TaskRepository();
const taskEvents = new TaskEventEmitter();
taskEvents.on('task:created', (task) =>
console.log(`đ Task created: ${task.title}`)
);
taskEvents.on('task:completed', (task) =>
console.log(`â
Task completed: ${task.title}`)
);
taskEvents.on('task:undone', (task) =>
console.log(`âŠī¸ Action undone: ${task.title}`)
);
const taskService = new TaskService(taskRepo, taskEvents);
const task1 = taskService.createTask('Learn Patterns', 'Study design patterns');
const task2 = taskService.createTask('Build App', 'Create example application');
taskService.completeTask(task1.id);
console.log(
'\nPending tasks:',
taskService.getPendingTasks().map((t) => t.title)
);
taskService.undoLastAction();
console.log(
'Pending after undo:',
taskService.getPendingTasks().map((t) => t.title)
);
console.log('\n========================================');
console.log('End of Class Patterns Examples');
console.log('========================================');