Skip to content

Part 2: Defining Abstractions ​

Once a system grows beyond what one person can keep track of, a design has to keep working as the system changes.

In Part 1, programs were small enough for one person to keep the whole design in mind. We maintained invariants with carefully written constructor functions, with programmer discipline about how objects were created and changed, and in Chapter 4 by hiding state inside closures, a pattern the language gave us no direct support for.

Part 2 widens the scope. Real software is built by teams, maintained for years, and solves problems too large for one person. No single person can keep track of every contributor, remember the whole history of the code, or review all of it. We cannot assume that other programmers will use our code correctly, or that every invariant will survive through discipline alone.

So we move from programmer discipline to encoding invariants in the language itself. When classes and the abstractions built around them encode the invariants, the language enforces them instead of each programmer's care, and the way programmers coordinate becomes an explicit part of the design.

This part develops class-based abstractions as the mechanism for enforcing invariants. Across seven chapters, we define classes, decompose systems into cohesive units, hide what is free to change, separate what a class means from how it stores it, depend on abstractions through interfaces, organise classes into hierarchies, and write code that keeps working as new types are added.

Intended Learning Objectives ​

By the end of Part 2, you will be able to:

  1. Design classes that own and protect state, using constructors, access modifiers, and methods to maintain invariants inside the object.
  2. Decompose a problem into cohesive classes, so each unit has a clear responsibility and the relationships among units are explicit.
  3. Use encapsulation and interfaces to hide change, exposing only the operations clients need while keeping representations free to evolve.
  4. Keep an implementation free to change, distinguishing what an abstraction means from how it is represented, and judging what each commitment a class avoids costs it in return.
  5. Apply polymorphism and extension deliberately, so new behaviour can be added by introducing new classes rather than reopening code that already works.

Chapter Overview ​

Part 2 covers three connected themes.

Building Abstractions ​

  1. Building Abstractions with Classes introduces classes as the direct language support for bundling state with the operations that maintain it.
  2. Decomposing Systems into Cohesive Classes shows how to split a system into classes and responsibilities that belong together.

Hiding What Can Change ​

  1. Encapsulating What Varies uses access control to keep a representation private, so the invariant that makes an object meaningful cannot be broken from outside.
  2. Preserving Implementation Freedom with Abstract Values treats the freedom to change an implementation as something a design can lose, showing how a careless comparison gives it away, and how immutable values and generic classes extend it beyond the representation.
  3. Defining Boundaries with Interfaces establishes narrow contracts that let clients depend on a stable shape rather than on a concrete implementation.

Designing for Growth ​

  1. Extending Behaviour Through Polymorphism uses inheritance and overriding to let related classes share behaviour while varying the parts that differ, and weighs inheritance against composition.
  2. Growing Systems with the Open/Closed Principle brings the design ideas together, adds the Dependency Inversion Principle, and shows how polymorphism lets software grow by adding new code instead of rewriting code that already works.

Toward Part 3: Evolution ​

Part 2 ends with a design goal that large systems depend on: adding new kinds of behaviour without reopening code that already works. Part 3 builds on this. It examines how the parts of a system depend on one another, how concrete implementations are supplied from outside, how a program depends on and publishes contracts across its boundaries, and how working systems are refactored, debugged, and extended.