Core Design Principles

Intermediate · 18 min read · Low-Level Design, Design Principles

Encapsulate what varies, program to interfaces, composition over inheritance, DRY, KISS, YAGNI and more.

SOLID gets the attention, but a few older, simpler principles do most of the work in day-to-day design — and they're the ideas design patterns are built from. If you understand these, most patterns stop feeling like recipes to memorise and start feeling like obvious consequences.

The principles in one line each

Each one is explained below with a short before-and-after.

Encapsulate what varies

Change is inevitable, so isolate the parts that change behind a boundary. Then a change hits one module, not the whole codebase — like watertight compartments in a ship.

Program to an interface, not an implementation

At design level: when class A needs B, ask what does A actually need from B? Describe that as an interface, and make A depend on it. You can still pass the original class, but you've created a seam where alternatives, decorators and test doubles can be plugged in.

Favour composition over inheritance

Inheritance is the obvious way to reuse code, but it has sharp edges:

High cohesion, low coupling

Nearly every principle here is a way to increase cohesion or reduce coupling. When two designs seem equally valid, pick the one with less coupling.

DRY, KISS, YAGNI

Law of Demeter (principle of least knowledge)

A method should only talk to its immediate collaborators: its own fields, its parameters, objects it creates. Avoid chains that reach through objects:

Tell, don't ask

Rather than pulling data out of an object to make a decision, tell the object to do the work:

Separation of concerns and layering

Divide the program into parts that each address one concern. In machine-coding rounds, a simple layering goes a long way:

GRASP: assigning responsibilities

Craig Larman's GRASP principles answer "which class should own this method?":

Interview questions

  • Why favour composition over inheritance? When is inheritance still the right choice?
  • Isn't DRY sometimes harmful?
  • What's wrong with order.getCustomer().getAddress().getCity()?

Further reading

Key takeaways

  • Encapsulate what varies so changes stay local.
  • Program to interfaces at real seams: external systems, varying algorithms, testing boundaries.
  • Favour composition for multi-dimensional variation and runtime flexibility; keep inheritance for true subtypes.
  • Aim for high cohesion and low coupling; use DRY, KISS and YAGNI to avoid both duplication and over-engineering.
  • Assign responsibilities to the information expert; tell objects what to do instead of asking for their data.

Previous: SOLID Principles · Next: Singleton