javascript
examples
examples.jsβ‘javascript
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// β MODULE DESIGN PATTERNS β
// β Singleton, Factory, Facade, Dependency Injection β
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
/*
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MODULE DESIGN PATTERNS OVERVIEW β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Patterns covered: β
β β
β 1. Singleton Pattern - Single instance across application β
β 2. Factory Pattern - Create objects without specifying class β
β 3. Facade Pattern - Simplified interface to complex system β
β 4. Observer Pattern - Publish/Subscribe for loose coupling β
β 5. Dependency Injection - Decouple dependencies for testing β
β 6. Adapter Pattern - Make incompatible interfaces work together β
β 7. Strategy Pattern - Interchangeable algorithms β
β 8. Repository Pattern - Abstract data access β
β β
β These patterns help create: β
β β’ Maintainable code β
β β’ Testable modules β
β β’ Loose coupling β
β β’ Clear interfaces β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*/
console.log('Module Design Patterns - Examples');
console.log('==================================\n');
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// 1. SINGLETON PATTERN
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
/*
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SINGLETON PATTERN β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Ensures a class has only ONE instance and provides global access β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β β
β β ββββββββββββββββββββ β β
β β β Singleton β β β
β β ββββββββββββββββββββ€ β β
β β β - instance β βββββ Only ONE instance exists β β
β β ββββββββββββββββββββ€ β β
β β β + getInstance() β βββββ Global access point β β
β β β + doSomething() β β β
β β ββββββββββββββββββββ β β
β β β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β Use cases: β
β β’ Configuration management β
β β’ Database connections β
β β’ Logging services β
β β’ Application state β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*/
// Method 1: ES Module Singleton (simplest - modules are singletons by default!)
const configSingleton = {
apiUrl: 'https://api.example.com',
debug: true,
version: '1.0.0',
get(key) {
return this[key];
},
set(key, value) {
this[key] = value;
},
};
// When imported, this is always the SAME instance
// export default configSingleton;
// Method 2: Class-based Singleton
class DatabaseConnection {
static #instance = null;
constructor() {
if (DatabaseConnection.#instance) {
return DatabaseConnection.#instance;
}
this.connection = null;
this.isConnected = false;
DatabaseConnection.#instance = this;
}
static getInstance() {
if (!DatabaseConnection.#instance) {
DatabaseConnection.#instance = new DatabaseConnection();
}
return DatabaseConnection.#instance;
}
connect(connectionString) {
if (this.isConnected) {
console.log(' Already connected');
return this;
}
console.log(` Connecting to: ${connectionString}`);
this.connection = connectionString;
this.isConnected = true;
return this;
}
disconnect() {
this.isConnected = false;
this.connection = null;
console.log(' Disconnected');
return this;
}
query(sql) {
if (!this.isConnected) {
throw new Error('Not connected');
}
console.log(` Executing: ${sql}`);
return { results: [] };
}
}
// Method 3: Closure-based Singleton
const Logger = (function () {
let instance = null;
class LoggerClass {
constructor() {
this.logs = [];
this.level = 'info';
}
setLevel(level) {
this.level = level;
}
log(message) {
const entry = { timestamp: new Date(), level: this.level, message };
this.logs.push(entry);
console.log(` [${entry.level.toUpperCase()}] ${message}`);
}
getLogs() {
return [...this.logs];
}
}
return {
getInstance() {
if (!instance) {
instance = new LoggerClass();
}
return instance;
},
};
})();
// Test Singleton
console.log('1. Singleton Pattern:');
// Database singleton
const db1 = DatabaseConnection.getInstance();
const db2 = DatabaseConnection.getInstance();
console.log(' Same instance:', db1 === db2); // true
db1.connect('mongodb://localhost:27017');
db2.query('SELECT * FROM users'); // Works - same instance
// Logger singleton
const logger1 = Logger.getInstance();
const logger2 = Logger.getInstance();
logger1.log('First message');
console.log(' Logger same instance:', logger1 === logger2);
console.log('');
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// 2. FACTORY PATTERN
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
/*
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FACTORY PATTERN β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Creates objects without exposing the instantiation logic β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β β
β β Client ββββΊ Factory ββββ¬ββββΊ ProductA β β
β β βββββΊ ProductB β β
β β βββββΊ ProductC β β
β β β β
β β Client doesn't know which class was instantiated β β
β β β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β Use cases: β
β β’ Creating different types of objects based on input β
β β’ Complex object creation logic β
β β’ Decoupling object creation from usage β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*/
// Example 1: Simple Factory
class UserFactory {
static create(type, data) {
switch (type) {
case 'admin':
return new AdminUser(data);
case 'customer':
return new CustomerUser(data);
case 'guest':
return new GuestUser(data);
default:
throw new Error(`Unknown user type: ${type}`);
}
}
}
class AdminUser {
constructor({ name, email }) {
this.name = name;
this.email = email;
this.role = 'admin';
this.permissions = ['read', 'write', 'delete', 'manage'];
}
getInfo() {
return `Admin: ${this.name} (${this.email})`;
}
}
class CustomerUser {
constructor({ name, email }) {
this.name = name;
this.email = email;
this.role = 'customer';
this.permissions = ['read', 'write'];
}
getInfo() {
return `Customer: ${this.name} (${this.email})`;
}
}
class GuestUser {
constructor({ name = 'Guest' } = {}) {
this.name = name;
this.role = 'guest';
this.permissions = ['read'];
}
getInfo() {
return `Guest: ${this.name}`;
}
}
// Example 2: Abstract Factory
const NotificationFactory = {
// Factory for creating notifications based on channel
createNotification(channel, message) {
const factories = {
email: () => ({
type: 'email',
message,
send() {
console.log(` Email: ${message}`);
},
}),
sms: () => ({
type: 'sms',
message,
send() {
console.log(` SMS: ${message}`);
},
}),
push: () => ({
type: 'push',
message,
send() {
console.log(` Push: ${message}`);
},
}),
slack: () => ({
type: 'slack',
message,
send() {
console.log(` Slack: ${message}`);
},
}),
};
const factory = factories[channel];
if (!factory) {
throw new Error(`Unknown channel: ${channel}`);
}
return factory();
},
// Create multiple notifications at once
createMultiple(channels, message) {
return channels.map((channel) => this.createNotification(channel, message));
},
};
// Test Factory
console.log('2. Factory Pattern:');
// User factory
const admin = UserFactory.create('admin', {
name: 'John',
email: 'john@admin.com',
});
const customer = UserFactory.create('customer', {
name: 'Jane',
email: 'jane@example.com',
});
const guest = UserFactory.create('guest');
console.log(' ' + admin.getInfo());
console.log(' ' + customer.getInfo());
console.log(' ' + guest.getInfo());
// Notification factory
console.log(' Sending notifications:');
const notifications = NotificationFactory.createMultiple(
['email', 'push'],
'Hello!'
);
notifications.forEach((n) => n.send());
console.log('');
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// 3. FACADE PATTERN
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
/*
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FACADE PATTERN β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Provides a simplified interface to a complex subsystem β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β β
β β Client ββββΊ Facade ββββ¬ββββΊ SubsystemA β β
β β βββββΊ SubsystemB β β
β β βββββΊ SubsystemC β β
β β βββββΊ SubsystemD β β
β β β β
β β Client only knows about Facade's simple interface β β
β β β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β Use cases: β
β β’ Simplifying complex APIs β
β β’ Wrapping legacy code β
β β’ Creating unified interface for multiple services β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*/
// Complex subsystems
class AuthService {
login(email, password) {
console.log(' AuthService: Authenticating...');
return { userId: '123', token: 'abc123' };
}
logout(token) {
console.log(' AuthService: Logging out...');
return true;
}
validateToken(token) {
return token && token.length > 0;
}
}
class UserProfileService {
getProfile(userId) {
console.log(' UserProfileService: Fetching profile...');
return { userId, name: 'John Doe', email: 'john@example.com' };
}
updateProfile(userId, data) {
console.log(' UserProfileService: Updating profile...');
return { ...data, userId };
}
}
class PermissionService {
getPermissions(userId) {
console.log(' PermissionService: Getting permissions...');
return ['read', 'write', 'delete'];
}
checkPermission(userId, permission) {
return this.getPermissions(userId).includes(permission);
}
}
class SessionService {
createSession(userId, token) {
console.log(' SessionService: Creating session...');
return { sessionId: 'sess_123', userId, token, createdAt: new Date() };
}
destroySession(sessionId) {
console.log(' SessionService: Destroying session...');
return true;
}
}
// Facade - simplified interface
class UserFacade {
constructor() {
this.authService = new AuthService();
this.profileService = new UserProfileService();
this.permissionService = new PermissionService();
this.sessionService = new SessionService();
}
// Simple login that handles everything
login(email, password) {
console.log(' UserFacade.login():');
// Authenticate
const auth = this.authService.login(email, password);
// Get profile
const profile = this.profileService.getProfile(auth.userId);
// Get permissions
const permissions = this.permissionService.getPermissions(auth.userId);
// Create session
const session = this.sessionService.createSession(auth.userId, auth.token);
// Return unified result
return {
user: profile,
permissions,
session: session.sessionId,
token: auth.token,
};
}
// Simple logout
logout(sessionId, token) {
console.log(' UserFacade.logout():');
this.sessionService.destroySession(sessionId);
this.authService.logout(token);
return { success: true };
}
// Get user with all data
getUser(userId) {
const profile = this.profileService.getProfile(userId);
const permissions = this.permissionService.getPermissions(userId);
return { ...profile, permissions };
}
}
// Test Facade
console.log('3. Facade Pattern:');
const userFacade = new UserFacade();
const loginResult = userFacade.login('john@example.com', 'password123');
console.log(' Login result:', {
user: loginResult.user.name,
permissions: loginResult.permissions.length,
});
console.log('');
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// 4. OBSERVER PATTERN (Pub/Sub)
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
/*
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β OBSERVER / PUB-SUB PATTERN β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Defines a subscription mechanism for events β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β β
β β Publisher ββemitβββΊ EventBus ββnotifyβββΊ Subscriber A β β
β β β β β
β β ββββββββnotifyβββΊ Subscriber B β β
β β β β β
β β ββββββββnotifyβββΊ Subscriber C β β
β β β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β Use cases: β
β β’ Event-driven architecture β
β β’ Decoupled components β
β β’ UI state management β
β β’ Real-time updates β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*/
class EventBus {
constructor() {
this.events = new Map();
}
// Subscribe to an event
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);
}
// Subscribe once
once(event, callback) {
const wrapper = (...args) => {
this.off(event, wrapper);
callback(...args);
};
return this.on(event, wrapper);
}
// Unsubscribe
off(event, callback) {
const callbacks = this.events.get(event);
if (callbacks) {
callbacks.delete(callback);
}
}
// Emit event
emit(event, data) {
const callbacks = this.events.get(event);
if (callbacks) {
callbacks.forEach((callback) => {
try {
callback(data);
} catch (error) {
console.error(`Error in event handler for ${event}:`, error);
}
});
}
}
// Get subscriber count
listenerCount(event) {
return this.events.get(event)?.size || 0;
}
// Remove all listeners for an event
removeAllListeners(event) {
if (event) {
this.events.delete(event);
} else {
this.events.clear();
}
}
}
// Global event bus (singleton)
const eventBus = new EventBus();
// Example usage: Shopping cart with events
class ShoppingCart {
constructor(eventBus) {
this.items = [];
this.eventBus = eventBus;
}
addItem(item) {
this.items.push(item);
this.eventBus.emit('cart:itemAdded', { item, cart: this });
this.eventBus.emit('cart:updated', {
items: this.items,
total: this.getTotal(),
});
}
removeItem(itemId) {
const index = this.items.findIndex((i) => i.id === itemId);
if (index > -1) {
const item = this.items.splice(index, 1)[0];
this.eventBus.emit('cart:itemRemoved', { item, cart: this });
this.eventBus.emit('cart:updated', {
items: this.items,
total: this.getTotal(),
});
}
}
getTotal() {
return this.items.reduce((sum, item) => sum + item.price, 0);
}
}
// Test Observer
console.log('4. Observer Pattern:');
const cart = new ShoppingCart(eventBus);
// Subscribe to events
const unsubscribe = eventBus.on('cart:itemAdded', ({ item }) => {
console.log(` Item added: ${item.name} - $${item.price}`);
});
eventBus.on('cart:updated', ({ total }) => {
console.log(` Cart total: $${total}`);
});
// Add items
cart.addItem({ id: 1, name: 'Laptop', price: 999 });
cart.addItem({ id: 2, name: 'Mouse', price: 29 });
// Unsubscribe from one event
unsubscribe();
cart.addItem({ id: 3, name: 'Keyboard', price: 79 }); // Won't log "Item added"
console.log('');
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// 5. DEPENDENCY INJECTION PATTERN
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
/*
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DEPENDENCY INJECTION PATTERN β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Inject dependencies instead of creating them inside the class β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β β
β β β Without DI: β With DI: β β
β β class Service { class Service { β β
β β db = new Database(); constructor(db) { β β
β β } this.db = db; β β
β β } β β
β β Hard to test! } β β
β β Easy to mock! β β
β β β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β Benefits: β
β β’ Easier testing (inject mocks) β
β β’ Loose coupling β
β β’ Flexibility to swap implementations β
β β’ Better separation of concerns β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*/
// Simple DI Container
class DIContainer {
constructor() {
this.services = new Map();
this.singletons = new Map();
}
// Register a service
register(name, factory, options = {}) {
this.services.set(name, {
factory,
singleton: options.singleton || false,
});
return this;
}
// Resolve a service
resolve(name) {
const service = this.services.get(name);
if (!service) {
throw new Error(`Service not found: ${name}`);
}
// Return singleton if exists
if (service.singleton && this.singletons.has(name)) {
return this.singletons.get(name);
}
// Create instance
const instance = service.factory(this);
// Cache singleton
if (service.singleton) {
this.singletons.set(name, instance);
}
return instance;
}
// Check if service exists
has(name) {
return this.services.has(name);
}
}
// Example: Service with dependencies
class EmailService {
send(to, subject, body) {
console.log(` Sending email to ${to}: ${subject}`);
return { sent: true, to, subject };
}
}
class LoggerService {
log(message) {
console.log(` [LOG] ${message}`);
}
}
// Service that depends on others
class OrderService {
constructor(emailService, logger) {
this.emailService = emailService;
this.logger = logger;
}
placeOrder(order) {
this.logger.log(`Processing order ${order.id}`);
// Process order...
this.emailService.send(
order.customerEmail,
'Order Confirmation',
`Your order ${order.id} has been placed!`
);
this.logger.log(`Order ${order.id} completed`);
return { success: true, orderId: order.id };
}
}
// Setup DI container
const container = new DIContainer();
container
.register('logger', () => new LoggerService(), { singleton: true })
.register('email', () => new EmailService(), { singleton: true })
.register(
'orderService',
(c) => new OrderService(c.resolve('email'), c.resolve('logger'))
);
// Test DI
console.log('5. Dependency Injection:');
const orderService = container.resolve('orderService');
orderService.placeOrder({
id: 'ORD-001',
customerEmail: 'customer@example.com',
items: [{ name: 'Widget', quantity: 2 }],
});
// For testing, you can inject mocks:
console.log(' With mock (testing):');
const mockEmail = { send: () => console.log(' Mock email sent!') };
const mockLogger = { log: (msg) => console.log(` [MOCK LOG] ${msg}`) };
const testOrderService = new OrderService(mockEmail, mockLogger);
testOrderService.placeOrder({ id: 'TEST-001', customerEmail: 'test@test.com' });
console.log('');
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// 6. ADAPTER PATTERN
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
/*
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ADAPTER PATTERN β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Makes incompatible interfaces work together β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β β
β β Client ββββΊ Adapter ββββΊ Adaptee β β
β β β β β
β β Translates between interfaces β β
β β β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β Use cases: β
β β’ Integrating third-party APIs β
β β’ Legacy code integration β
β β’ Standardizing different implementations β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*/
// Old API (legacy)
class OldPaymentSystem {
processPayment(amount, cardNumber, cvv, expiry) {
console.log(` OldPayment: Processing $${amount}`);
return { status: 'success', transactionId: 'OLD-' + Date.now() };
}
}
// New API (expected interface)
// interface PaymentProcessor {
// pay(paymentDetails: PaymentDetails): PaymentResult
// }
// Adapter to make old system work with new interface
class PaymentAdapter {
constructor(oldSystem) {
this.oldSystem = oldSystem;
}
// New interface method
pay(paymentDetails) {
const { amount, card } = paymentDetails;
// Adapt to old interface
const result = this.oldSystem.processPayment(
amount,
card.number,
card.cvv,
card.expiry
);
// Adapt response to new format
return {
success: result.status === 'success',
transactionId: result.transactionId,
amount: amount,
};
}
}
// Another adapter for a different payment provider
class StripeAdapter {
constructor() {
// Would connect to Stripe API
}
pay(paymentDetails) {
console.log(` Stripe: Processing $${paymentDetails.amount}`);
return {
success: true,
transactionId: 'STRIPE-' + Date.now(),
amount: paymentDetails.amount,
};
}
}
// Test Adapter
console.log('6. Adapter Pattern:');
// Use old system through adapter
const oldSystem = new OldPaymentSystem();
const paymentProcessor = new PaymentAdapter(oldSystem);
const result = paymentProcessor.pay({
amount: 99.99,
card: { number: '4111111111111111', cvv: '123', expiry: '12/25' },
});
console.log(' Payment result:', result);
// Can easily swap to different adapter
const stripeProcessor = new StripeAdapter();
const stripeResult = stripeProcessor.pay({ amount: 49.99 });
console.log(' Stripe result:', stripeResult);
console.log('');
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// 7. STRATEGY PATTERN
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
/*
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STRATEGY PATTERN β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Define family of algorithms and make them interchangeable β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β β
β β Context ββββΊ Strategy Interface β β
β β β² β β
β β βββββββββββββΌββββββββββββ β β
β β β β β β β
β β StrategyA StrategyB StrategyC β β
β β β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β Use cases: β
β β’ Different sorting algorithms β
β β’ Payment methods β
β β’ Compression algorithms β
β β’ Validation strategies β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*/
// Shipping strategies
const shippingStrategies = {
standard: {
name: 'Standard Shipping',
calculate(weight, distance) {
return weight * 0.5 + distance * 0.1;
},
estimatedDays: 7,
},
express: {
name: 'Express Shipping',
calculate(weight, distance) {
return weight * 1.0 + distance * 0.3 + 10;
},
estimatedDays: 3,
},
overnight: {
name: 'Overnight Shipping',
calculate(weight, distance) {
return weight * 2.0 + distance * 0.5 + 25;
},
estimatedDays: 1,
},
pickup: {
name: 'Store Pickup',
calculate() {
return 0;
},
estimatedDays: 0,
},
};
// Context that uses strategies
class ShippingCalculator {
constructor(strategy = shippingStrategies.standard) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
calculate(order) {
const weight = order.items.reduce((sum, item) => sum + item.weight, 0);
const distance = order.distance || 100;
const cost = this.strategy.calculate(weight, distance);
return {
method: this.strategy.name,
cost: cost.toFixed(2),
estimatedDays: this.strategy.estimatedDays,
};
}
}
// Test Strategy
console.log('7. Strategy Pattern:');
const order = {
items: [
{ name: 'Laptop', weight: 3 },
{ name: 'Mouse', weight: 0.2 },
],
distance: 150,
};
const calculator = new ShippingCalculator();
// Try different strategies
Object.entries(shippingStrategies).forEach(([key, strategy]) => {
calculator.setStrategy(strategy);
const result = calculator.calculate(order);
console.log(
` ${result.method}: $${result.cost} (${result.estimatedDays} days)`
);
});
console.log('');
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// 8. REPOSITORY PATTERN
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
/*
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β REPOSITORY PATTERN β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Abstracts data access behind a collection-like interface β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β β
β β Business Logic ββββΊ Repository Interface β β
β β β² β β
β β βββββββββββββββββΌββββββββββββββββ β β
β β β β β β β
β β SQLRepository MongoRepository InMemoryRepository β β
β β β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β Benefits: β
β β’ Decouple business logic from data access β
β β’ Easy to swap storage implementations β
β β’ Easier to test with in-memory repository β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*/
// Repository interface (conceptual)
class Repository {
findById(id) {
throw new Error('Not implemented');
}
findAll() {
throw new Error('Not implemented');
}
create(entity) {
throw new Error('Not implemented');
}
update(id, entity) {
throw new Error('Not implemented');
}
delete(id) {
throw new Error('Not implemented');
}
}
// In-memory implementation (for testing/development)
class InMemoryUserRepository extends Repository {
constructor() {
super();
this.users = new Map();
this.nextId = 1;
}
findById(id) {
return this.users.get(id) || null;
}
findAll() {
return Array.from(this.users.values());
}
findByEmail(email) {
return this.findAll().find((u) => u.email === email);
}
create(userData) {
const user = {
id: this.nextId++,
...userData,
createdAt: new Date(),
};
this.users.set(user.id, user);
return user;
}
update(id, userData) {
const user = this.users.get(id);
if (!user) return null;
const updated = { ...user, ...userData, updatedAt: new Date() };
this.users.set(id, updated);
return updated;
}
delete(id) {
return this.users.delete(id);
}
}
// API implementation (for production)
class ApiUserRepository extends Repository {
constructor(apiUrl) {
super();
this.apiUrl = apiUrl;
}
async findById(id) {
// Would make API call
console.log(` API: GET ${this.apiUrl}/users/${id}`);
return { id, name: 'API User' };
}
async findAll() {
console.log(` API: GET ${this.apiUrl}/users`);
return [];
}
async create(userData) {
console.log(` API: POST ${this.apiUrl}/users`);
return { id: Date.now(), ...userData };
}
async update(id, userData) {
console.log(` API: PUT ${this.apiUrl}/users/${id}`);
return { id, ...userData };
}
async delete(id) {
console.log(` API: DELETE ${this.apiUrl}/users/${id}`);
return true;
}
}
// Service using repository
class UserService {
constructor(userRepository) {
this.repo = userRepository;
}
getUser(id) {
return this.repo.findById(id);
}
createUser(data) {
// Business logic here
if (!data.email) {
throw new Error('Email is required');
}
return this.repo.create(data);
}
getAllUsers() {
return this.repo.findAll();
}
}
// Test Repository
console.log('8. Repository Pattern:');
// Use in-memory for testing
const memoryRepo = new InMemoryUserRepository();
const userService = new UserService(memoryRepo);
const user1 = userService.createUser({
name: 'John',
email: 'john@example.com',
});
const user2 = userService.createUser({
name: 'Jane',
email: 'jane@example.com',
});
console.log(
' Created users:',
userService
.getAllUsers()
.map((u) => u.name)
.join(', ')
);
console.log(' Find by ID:', userService.getUser(1)?.name);
// For production, swap to API repository
console.log(' API Repository:');
const apiRepo = new ApiUserRepository('https://api.example.com');
const prodService = new UserService(apiRepo);
prodService.getUser(1);
console.log('');
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// SUMMARY
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
console.log(`
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MODULE DESIGN PATTERNS - COMPLETE β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£
β β
β Patterns Covered: β
β β
β βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Pattern β Purpose β β
β βββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β
β β Singleton β Single instance, global access β β
β β Factory β Create objects without specifying class β β
β β Facade β Simplified interface to complex system β β
β β Observer β Event-driven communication β β
β β DI β Inject dependencies for testing β β
β β Adapter β Make incompatible interfaces work β β
β β Strategy β Interchangeable algorithms β β
β β Repository β Abstract data access β β
β βββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β When to Use: β
β β’ Singleton: Config, DB connections, logging β
β β’ Factory: Creating different types based on input β
β β’ Facade: Simplifying complex APIs β
β β’ Observer: Decoupled event handling β
β β’ DI: Testable, loosely-coupled code β
β β’ Adapter: Third-party integrations β
β β’ Strategy: Swappable algorithms β
β β’ Repository: Data access abstraction β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
`);