Scalable Business for Startups
Get the oars in the water and start rowing. Execution is the single biggest factor in achievement so the faster and better your execution.
Chapter 19: JavaScript Design Patterns
Introduction to Design Patterns
- Definition of Design Patterns
- A design pattern is a reusable solution to a commonly occurring problem within a given context in software design.
- Importance of Design Patterns
- Provides standardized methods for solving issues.
- Enhances code readability and maintainability.
- Facilitates communication among developers.
- Categories of Design Patterns
- Creational Patterns
- Structural Patterns
- Behavioral Patterns
Section 1: Creational Patterns
Creational patterns deal with object creation mechanisms, trying to create objects in a manner suitable to the situation.
1.1 Singleton Pattern
- Description
- Ensures a class has only one instance and provides a global point of access to it.
- Implementation Example
class Singleton { constructor() { if (Singleton.instance) { return Singleton.instance; } Singleton.instance = this; this.timestamp = new Date(); } } const instance1 = new Singleton(); const instance2 = new Singleton(); console.log(instance1 === instance2); // true console.log(instance1.timestamp); // shows the same timestamp for both instances
1.2 Factory Pattern
- Description
- Provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created.
- Implementation Example
class Car { constructor(make, model) { this.make = make; this.model = model; } } class CarFactory { static createCar(make, model) { return new Car(make, model); } } const car1 = CarFactory.createCar('Toyota', 'Corolla'); console.log(car1); // Car { make: 'Toyota', model: 'Corolla' }
1.3 Constructor Pattern
- Description
- Uses a constructor function to create an object and return the instance of the object.
- Implementation Example
function Car(make, model) { this.make = make; this.model = model; } const myCar = new Car('Honda', 'Civic'); console.log(myCar); // Car { make: 'Honda', model: 'Civic' }
Section 2: Structural Patterns
Structural patterns deal with object composition and help ensure that if one part of a system changes, the entire system doesn't need to change.
2.1 Adapter Pattern
- Description
- Allows incompatible interfaces to work together by converting the interface of a class into another interface clients expect.
- Implementation Example
class OldSystem { request() { return 'Request from Old System'; } } class NewSystem { specificRequest() { return 'Request from New System'; } } class Adapter { constructor(newSystem) { this.newSystem = newSystem; } request() { return this.newSystem.specificRequest(); } } const oldSystem = new OldSystem(); const newSystem = new NewSystem(); const adapter = new Adapter(newSystem); console.log(oldSystem.request()); // Request from Old System console.log(adapter.request()); // Request from New System
2.2 Decorator Pattern
- Description
- Allows behavior to be added to individual objects, either statically or dynamically, without affecting the behavior of other objects from the same class.
- Implementation Example
class Coffee { cost() { return 5; } } class MilkDecorator { constructor(coffee) { this.coffee = coffee; } cost() { return this.coffee.cost() + 1; } } class SugarDecorator { constructor(coffee) { this.coffee = coffee; } cost() { return this.coffee.cost() + 0.5; } } const coffee = new Coffee(); const milkCoffee = new MilkDecorator(coffee); const milkSugarCoffee = new SugarDecorator(milkCoffee); console.log(milkSugarCoffee.cost()); // 6.5
Section 3: Behavioral Patterns
Behavioral patterns focus on communication between objects, what goes on between objects, and how they communicate.
3.1 Observer Pattern
- Description
- Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
- Implementation Example
class Subject { 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(`Observer received data: ${data}`); } } const subject = new Subject(); const observer1 = new Observer(); const observer2 = new Observer(); subject.subscribe(observer1); subject.subscribe(observer2); subject.notify('New Data'); // Both observers receive the data
3.2 Command Pattern
- Description
- Encapsulates a request as an object, thereby allowing for parameterization of clients with queues, requests, and operations.
- Implementation Example
class Command { execute() { console.log('Executing command'); } } class CommandInvoker { constructor() { this.commands = []; } storeCommand(command) { this.commands.push(command); } executeCommands() { this.commands.forEach(command => command.execute()); } } const command1 = new Command(); const invoker = new CommandInvoker(); invoker.storeCommand(command1); invoker.executeCommands(); // Executing command
Conclusion
- Summary of Design Patterns
- Reiterate the significance of design patterns in JavaScript development.
- Emphasize their role in improving code quality and facilitating better collaboration among developers.
- Encouragement to Explore Further
- Suggest resources for further learning.
- Encourage the reader to apply these patterns in real-world projects for better understanding.