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)

PrincipleMeaningCore Idea
SRPSingle ResponsibilityA class should have only one reason to change. Each class should solve one problem.
OCPOpen/ClosedSoftware entities should be open for extension, but closed for modification.
LSPLiskov SubstitutionYou should be able to replace a base class with a subclass without breaking the code.
ISPInterface SegregationNo client should be forced to depend on methods it does not use.
DIPDependency InversionHigh-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, a Penguin subclass 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

CategoryItemDescription / Rule
DependencyCyclic DependencyAvoid; follow Acyclic Dependence Principle.
StrengthStrong: inheritance, implementation; Weak: member/method access.
Intra PackageRemove weak relations/independent classes; focus on core classes.
Inter PackageFan-in: Entry; Fan-out: Exit. Expand A/keep B for analysis.
Least KnowledgeReduce dependency count; "don’t talk to strangers."
PrinciplesSOLIDSRP (Responsibility), OCP (Open-Closed), LSP (Liskov), ISP (Segregation), DIP (Inversion).
StructuralFavor Composition Over Inheritance; Separation & Abstraction (DRY).
Design RulesKISS (Keep It Simple, Stupid); Don’t Repeat Yourself.
PracticesMethod LengthDecompose long methods; target one-page visibility.
Coding ChecklistMemory leaks, feature envy, naming, downcasting, comments, redundant code.
Safety/LogicInitialization, index scope, zero-division, termination, overflow, consistency.
LiteralsUse constants instead of magic numbers.

Copyright © 2026 Nut17. All rights reserved.