Overview
Design patterns are typical solutions to common problems in software design. They are like blueprints that you can customize to solve a particular design problem in your code. They are not finished designs, but templates for how to solve a problem.
Core Concepts
- Creational Patterns: Deal with object creation mechanisms, trying to solve the problem of creating objects in an existing system while hiding the logic of how they are created.
- Singleton: Ensures a class has only one instance and provides a global point of access to it.
- Factory Method: Provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.
- Abstract Factory: Lets you produce families of related objects without specifying their concrete classes.
- Builder: Used to construct complex objects step by step.
- Structural Patterns: Deal with how classes and objects are composed to form larger structures.
- Adapter: Allows incompatible interfaces to work together.
- Decorator: Dynamically adds behavior to an object without changing its implementation.
- Facade: Provides a simplified interface to a complex system of classes.
- Proxy: Provides a placeholder for another object to control access to it.
- Behavioral Patterns: Deal with how objects communicate and assign responsibilities.
- Observer: A subscription mechanism to notify multiple objects about any events that happen to the object they’re observing.
- Strategy: Defines a family of algorithms, encapsulates each one, and makes them interchangeable.
- Command: Turns a request into a stand-alone object that contains all information about the request.
- State: Allows an object to alter its behavior when its internal state changes.
Code Examples
// Strategy Pattern Example
interface Strategy {
execute(): void;
}
class PaymentWithCreditCard implements Strategy {
execute() { console.log("Paying with Credit Card"); }
}
class PaymentWithPayPal implements Strategy {
execute() { console.log("Paying with PayPal"); }
}
class PaymentContext {
private strategy: Strategy;
constructor(strategy: Strategy) { this.strategy = strategy; }
setStrategy(strategy: Strategy) { this.strategy = strategy; }
pay() { this.strategy.execute(); }
}
const context = new PaymentContext(new PaymentWithCreditCard());
context.pay(); // Paying with Credit Card
context.setStrategy(new PaymentWithPayPal());
context.pay(); // Paying with PayPal
Use Cases
- Consistency: Ensuring a common vocabulary among developers.
- Maintainability: Decoupling components to make them easier to change without affecting other parts of the system.
- Reusability: Applying proven solutions to recurring problems.
Gotchas
- Over-Engineering: Applying patterns where they aren’t needed (the “Golden Hammer” fallacy).
- Complexity: Introducing additional classes and interfaces can make the code harder to follow if the pattern is overkill for the problem.
