Software Development
System Design
Common software design patterns and system concepts.
Design Patterns
Creational Patterns
Focused on flexible object creation.
// Singleton: Ensure only one instance exists
class Singleton {
private static instance: Singleton;
private constructor() {}
static getInstance() { return this.instance ||= new Singleton(); }
}
// Builder: Step-by-step construction of complex objects
class HouseBuilder {
private house = new House();
buildWalls() { /*...*/ return this; }
buildRoof() { /*...*/ return this; }
getHouse() { return this.house; }
}
// Factory Method: Delegate instantiation to subclasses
abstract class Creator {
abstract createProduct(): Product;
someOperation() { const p = this.createProduct(); p.doStuff(); }
}
// Abstract Factory: Create families of related objects
interface GUIFactory { createButton(): Button; createCheckbox(): Checkbox; }
class WinFactory implements GUIFactory {
createButton() { return new WinButton(); }
createCheckbox() { return new WinCheckbox(); }
}
Structural Patterns
Concerned with composition and relationships between entities.
// Adapter (Object): Wrap incompatible interface to match expected one
class Adapter implements Target {
constructor(private adaptee: Adaptee) {}
request() { this.adaptee.specificRequest(); }
}
// Composite: Tree structure (Parts and Wholes treated same)
interface Node { operation(): void }
class Leaf implements Node { operation() {} }
class Composite implements Node {
children: Node[] = [];
operation() { this.children.forEach(c => c.operation()); }
}
// Decorator: Attach new behaviors by wrapping
class Base { op() {} }
class Decorator extends Base {
constructor(private component: Base) { super() }
op() { this.component.op(); /* extra logic */ }
}
// Facade: Simple entry point to complex subsystem
class ComputerFacade {
constructor(private cpu = new CPU(), private ram = new RAM()) {}
start() { this.cpu.freeze(); this.ram.load(); }
}
// Flyweight: Share common state to save memory
class FlyweightFactory {
private cache: Record<string, Flyweight> = {};
getFlyweight(key: string) { return this.cache[key] ||= new Flyweight(key); }
}
Behavioral Patterns
Focused on algorithm assignment and interaction between objects.
// Strategy: Interchangeable algorithms at runtime
class Context {
setStrategy(s: Strategy) { this.strategy = s; }
execute() { this.strategy.doWork(); }
}
// Template Method: Skeleton in base, details in subclasses
abstract class DataMiner {
mine() { this.open(); this.extract(); this.close(); }
abstract extract(): void;
open() { /* default behavior */ }
}
// State: Behavior changes based on internal state object
class Document {
setState(s: State) { this.state = s; }
publish() { this.state.publish(); }
}
// Observer (PubSub): Notify multiple objects of changes
class Subject {
subs: Observer[] = [];
notify() { this.subs.forEach(s => s.update()); }
}
// Iterator: Sequential traversal of collections
class ArrayIterator {
private index = 0;
next() { return this.items[this.index++]; }
hasNext() { return this.index < this.items.length; }
}
// Command: Encapsulate request as object (Supports Undo)
class SaveCommand implements Command {
constructor(private editor: Editor) {}
execute() { this.editor.save(); }
}
// Visitor: Add operations without modifying object class
interface Shape { accept(v: Visitor): void }
class Circle implements Shape { accept(v: Visitor) { v.visitCircle(this); } }
class ExportVisitor {
visitCircle(c: Circle) { /* export logic */ }
}
Core Principles (SOLID)
| Principle | Meaning | Core Idea |
|---|---|---|
| SRP | Single Responsibility | A class should have only one reason to change. Each class should solve one problem. |
| OCP | Open/Closed | Software entities should be open for extension, but closed for modification. |
| LSP | Liskov Substitution | You should be able to replace a base class with a subclass without breaking the code. |
| ISP | Interface Segregation | No client should be forced to depend on methods it does not use. |
| DIP | Dependency Inversion | High-level modules should not depend on low-level modules; both should depend on abstractions. |
- SRP: Prevents "God Objects". If a class manages both DB operations and UI rendering, split it.
- OCP: Add new features by creating new classes that implement an interface, instead of editing existing stable code.
- LSP: Subclasses must honor the contract of the parent. If
Bird.fly()exists, aPenguinsubclass shouldn't throw "Not Supported". - ISP: Split giant interfaces into smaller, specialized ones so clients only see what they need.
- DIP: High-level logic (e.g. Payment Flow) should depend on an interface (
IPaymentGateway), not a specific low-level tool (PaypalSDK).
Structural Design Philosophy
Beyond patterns, structural design is about how components are woven together.
- Favor Composition Over Inheritance: Inheritance ("is-a") creates tight coupling and rigid hierarchies. Composition ("has-a") allows you to swap behaviors at runtime and keeps classes focused.
- Least Knowledge (Law of Demeter): "Don't talk to strangers." An object should only call methods on its immediate dependencies. Avoid chains like
user.getAccount().getBalance().currency(). - Acyclic Dependency: Dependencies must never form a loop. If
A -> B -> C -> A, you can never test or deploy any of them in isolation. - Separation of Concerns: Keep different types of logic (infrastructure, domain, presentation) in distinct layers or packages.
Code Smell
| Category | Item | Description / Rule |
|---|---|---|
| Dependency | Cyclic Dependency | Avoid; follow Acyclic Dependence Principle. |
| Strength | Strong: inheritance, implementation; Weak: member/method access. | |
| Intra Package | Remove weak relations/independent classes; focus on core classes. | |
| Inter Package | Fan-in: Entry; Fan-out: Exit. Expand A/keep B for analysis. | |
| Least Knowledge | Reduce dependency count; "don’t talk to strangers." | |
| Principles | SOLID | SRP (Responsibility), OCP (Open-Closed), LSP (Liskov), ISP (Segregation), DIP (Inversion). |
| Structural | Favor Composition Over Inheritance; Separation & Abstraction (DRY). | |
| Design Rules | KISS (Keep It Simple, Stupid); Don’t Repeat Yourself. | |
| Practices | Method Length | Decompose long methods; target one-page visibility. |
| Coding Checklist | Memory leaks, feature envy, naming, downcasting, comments, redundant code. | |
| Safety/Logic | Initialization, index scope, zero-division, termination, overflow, consistency. | |
| Literals | Use constants instead of magic numbers. |