← Back to Programming

Thinking in objects

Writing classes is easy; designing good ones is a skill. Object-oriented design is about dividing a system into objects with clear responsibilities that work together.

From requirements to objects

Design starts with the problem. A common technique is to read the requirements and note nouns, which suggest candidate classes, and verbs, which suggest methods. For a library system: Book, Member, and Loan are classes; borrow, return, and reserve are behaviours. Not every noun deserves a class, so candidates are refined.

Responsibilities and cohesion

Each class should have a clear responsibility: what it knows and what it does. High cohesion means a class's parts belong together around one purpose. A Loan class that tracks dates and calculates fines is cohesive; one that also sends emails and prints reports is not.

Coupling

Coupling measures how much classes depend on each other's details. Low coupling is better: classes that interact only through small, stable interfaces can change independently. If changing a database table forces edits in a dozen unrelated classes, the design is tightly coupled.

Composition versus inheritance

Inheritance models 'is a' relationships; composition models 'has a', building objects from other objects. A Car has an Engine; it is not a kind of engine. Designers often prefer composition, because deep inheritance hierarchies become rigid: a change in a base class ripples through every subclass.

UML class diagrams

The Unified Modeling Language (UML) offers standard diagrams. A class diagram draws each class as a box with name, attributes, and methods. Lines show relationships: a hollow triangle arrow for inheritance, a plain line for association, and diamonds for aggregation and composition. Multiplicities such as 1 and * show how many objects relate.

Interfaces as contracts

Interfaces describe what an object can do without saying how. Code that depends on an interface, such as PaymentMethod with a pay method, works with any implementation: card, bank transfer, or a test double. This is often summarised as 'program to an interface, not an implementation'.

Tell, don't ask

Instead of asking an object for its data and making decisions outside it, tell the object what to do and let it decide. Rather than if (account.getBalance() >= amount) account.setBalance(account.getBalance() - amount), call account.withdraw(amount), which enforces its own rules. This keeps logic with the data it concerns.

Worked example

Designing a parking garage: classes might be Garage, Level, Space, Vehicle (with subclasses Car and Motorbike), and Ticket. A Garage has Levels; a Level has Spaces (composition). Space.canFit(vehicle) keeps size rules inside Space. Ticket records entry time and computes the fee. Swapping the fee policy is easier if Ticket uses a FeePolicy interface rather than hard-coded rates.

Key takeaways

Good design starts from requirements; nouns suggest classes, verbs suggest methods. Classes should be highly cohesive, with one clear responsibility, and loosely coupled. Prefer composition ('has a') over deep inheritance ('is a'). UML class diagrams show classes and relationships. Interfaces act as contracts; program to interfaces. 'Tell, don't ask' keeps behaviour with data.

Flashcard

Object-oriented design

Dividing a system into collaborating objects

Responsibilities.

Check your understanding

A Car class needs an engine. Why is composition (Car has an Engine) better than inheritance (Car extends Engine)?

← Previous lesson Next lesson →