What design patterns have you used in practice?
sobes.tech AI
Answer from AI
In practice, I have applied the following design patterns:
-
Factory Method: For creating objects of different types without specifying the exact class of the object to be created. Useful when working with various data sources or configurations.
// Example of using Factory Method class DataReader { read() { throw new Error("Method not implemented"); } } class FileReader extends DataReader { read() { console.log("Reading data from file..."); } } class NetworkReader extends DataReader { read() { console.log("Reading data from network..."); } } class DataReaderFactory { createReader(type) { switch (type) { case "file": return new FileReader(); case "network": return new NetworkReader(); default: throw new Error("Unknown reader type"); } } } const factory = new DataReaderFactory(); const fileReader = factory.createReader("file"); fileReader.read(); -
Singleton: To guarantee that only one instance of a class exists and to provide a global point of access to it. Often used for configuration managers or database connections.
// Example of singleton class ConfigManager { constructor() { if (ConfigManager.instance) { return ConfigManager.instance; } this.config = {}; ConfigManager.instance = this; } setConfig(key, value) { this.config[key] = value; } getConfig(key) { return this.config[key]; } } const instance1 = new ConfigManager(); const instance2 = new ConfigManager(); console.log(instance1 === instance2); // true -
Observer: To implement a "one-to-many" relationship, where when the state of one object (the publisher) changes, all dependent objects (subscribers) are automatically notified. Used in notification systems, reactive interfaces.
// Example of observer class Observable { constructor() { this.observers = []; } subscribe(observer) { this.observers.push(observer); } unsubscribe(observer) { this.observers = this.observers.filter(obs => obs !== observer); } notify(data) { this.observers.forEach(observer => observer.update(data)); } } class Observer { update(data) { console.log("Received update:", data); } } const observable = new Observable(); const observer1 = new Observer(); const observer2 = new Observer(); observable.subscribe(observer1); observable.subscribe(observer2); observable.notify("New data available!"); -
Strategy: To define a family of algorithms, encapsulate each one, and make them interchangeable. Allows the algorithm to vary independently from clients that use it. Used for different data processing or operation execution options.
// Example of strategy class PaymentStrategy { pay(amount) { throw new Error("Method not implemented"); } } class CreditCardPayment extends PaymentStrategy { pay(amount) { console.log(`Paying ${amount} using Credit Card`); } } class PayPalPayment extends PaymentStrategy { pay(amount) { console.log(`Paying ${amount} using PayPal`); } } class PaymentContext { setStrategy(strategy) { this.strategy = strategy; } executePayment(amount) { this.strategy.pay(amount); } } const context = new PaymentContext(); context.setStrategy(new CreditCardPayment()); context.executePayment(100); context.setStrategy(new PayPalPayment()); context.executePayment(50); -
Decorator: For dynamically adding new functionality to an object without changing its structure. Wraps an existing object, providing the same interface as the wrapped object but with added functionality. Useful for logging, validation, caching.
// Example of decorator class Component { operation() { return "Basic Component"; } } class Decorator { constructor(component) { this.component = component; } operation() { return this.component.operation(); } } class ConcreteDecoratorA extends Decorator { operation() { return `ConcreteDecoratorA(${super.operation()})`; } } class ConcreteDecoratorB extends Decorator { operation() { return `ConcreteDecoratorB(${super.operation()})`; } } const simple = new Component(); console.log(simple.operation()); const decoratorA = new ConcreteDecoratorA(simple); console.log(decoratorA.operation()); const decoratorB = new ConcreteDecoratorB(decoratorA); console.log(decoratorB.operation()); -
Module: Although Node.js has a built-in module system (CommonJS, ES Modules), the Module pattern in JavaScript (before native modules appeared) was used to create private members and a public interface. It forms the basis for building encapsulated code.
These patterns helped me create more structured, scalable, and maintainable code.