Low-level designCore6 min

Coupling and cohesion

Cohesion is how well the parts inside a class belong together; coupling is how much a class depends on others, and on what. Aim for classes whose insides belong together and whose dependencies are few and shallow, and a change stays in one place.

Watch it evolve1 / 8
The problem

An event, and a checkout

Event holds the venue, the attendees and the sponsors; Checkout quotes a price.

80
Start
Stage 1
Final

1 · The situation

An event, and everything about it

A community events app sells seats to meetups. An Event has a title, a venue, a list of attendees and its sponsors; a Checkout quotes a price, charging more for the last two seats. The first version puts everything about an event into one class, and the checkout works out the free seats itself:

One class for the event, and a checkout that reaches inJava
class Event {
    private final String title;
    private final String venueName;
    private final String venueAddress;
    private final int capacity;
    private final List<String> attendees = new ArrayList<>();
    private final List<String> sponsors = new ArrayList<>();

    Event(String title, String venueName, String venueAddress, int capacity) {
        this.title = title;
        this.venueName = venueName;
        this.venueAddress = venueAddress;
        this.capacity = capacity;
    }

    String title() {
        return title;
    }

    String venueName() {
        return venueName;
    }

    int capacity() {
        return capacity;
    }

    List<String> attendees() {
        return List.copyOf(attendees);
    }

    void register(String name) {
        attendees.add(name);
    }

    void addSponsor(String sponsor) {
        sponsors.add(sponsor);
    }

    String mapLink() {
        return "maps.example/?q=" + venueAddress.replace(' ', '+');
    }

    String sponsorBanner() {
        return "With thanks to " + String.join(", ", sponsors);
    }
}

class Checkout {
    // The last two seats cost more.
    String quote(Event e) {
        int left = e.capacity() - e.attendees().size();
        if (left <= 0) return e.title() + ": sold out";
        int dollars = left <= 2 ? 30 : 20;
        return e.title() + " at " + e.venueName() + ": seats left " + left + ", $" + dollars;
    }
}
Event− title: String− venueName: String− venueAddress: String− capacity: int− attendees: List<String>− sponsors: List<String>~ title(): String~ venueName(): String~ capacity(): int~ attendees(): List<String>~ register(String)~ addSponsor(String)~ mapLink(): String~ sponsorBanner(): StringCheckout~ quote(Event): String

Drawn from the code: one large Event, and a Checkout that depends on it.

Open Source Night at Harbor Hall: seats left 3, $20
Open Source Night at Harbor Hall: seats left 1, $30
Map: maps.example/?q=12+Harbor+Road
With thanks to Acme, Globex

2 · Low cohesion

Three things in one class

List which fields each of Event's methods uses, and the class falls apart into groups that never touch:

Methods

Fields they use

What they're about

venueName(), capacity(), mapLink()

venueName, venueAddress, capacity

The venue

register(), attendees()

attendees

Who's coming

addSponsor(), sponsorBanner()

sponsors

Who's paying for it

title()

title

The event itself

That's low cohesion: the parts of the class don't depend on each other. It has practical costs. The venue's details are copied into every event held there, so a changed address means editing every event. And anyone reading Event must wade through sponsorship to find seating.

3 · Tight coupling

A checkout that knows how seats are counted

Checkout.quote() computes the free seats itself: capacity minus the length of the attendee list. So it depends not just on Event but on how Event stores attendance. The day canceled registrations are kept in the list for refunds, or the hall adds standing room, the checkout quotes the wrong number of seats, though nobody edited it.

That's tight coupling: one class depends on another's details, so changes inside one ripple into the other. The practice checks notice the symptom: Checkout.quote mostly works on Event.

4 · The fix

Group what belongs together, depend on less

Venue keeps its name, address and capacity, and makes its own map link. One venue can now be shared by every event held there.

Java
class Venue {
    private final String name;
    private final String address;
    private final int capacity;

    Venue(String name, String address, int capacity) {
        this.name = name;
        this.address = address;
        this.capacity = capacity;
    }

    String name() {
        return name;
    }

    int capacity() {
        return capacity;
    }

    String mapLink() {
        return "maps.example/?q=" + address.replace(' ', '+');
    }
}
1Venue− name: String− address: String− capacity: int~ name(): String~ capacity(): int~ mapLink(): StringSponsorship− sponsors: List<String>~ add(String)~ banner(): StringEvent− title: String− venue: Venue− sponsorship: Sponsorship− attendees: List<String>~ title(): String~ where(): String~ sponsorship(): Sponsorship~ register(String)~ seatsLeft(): intCheckout~ quote(Event): String
Open Source Night at Harbor Hall: seats left 3, $20
Open Source Night at Harbor Hall: seats left 1, $30
Map: maps.example/?q=12+Harbor+Road
With thanks to Acme, Globex

The same quotes, map link and banner.

5 · What they are

Two measures of the same design

Cohesion is how strongly the parts inside a module belong together. Coupling is how much modules depend on each other. They often move together: pulling the venue out of Event raised Event's cohesion, and giving it seatsLeft() lowered the checkout's coupling, because the checkout now relies on one answer instead of two internals.

1Venue− name: String− address: String− capacity: int~ name(): String~ capacity(): int~ mapLink(): StringSponsorship− sponsors: List<String>~ add(String)~ banner(): StringEvent− title: String− venue: Venue− sponsorship: Sponsorship− attendees: List<String>~ title(): String~ where(): String~ sponsorship(): Sponsorship~ register(String)~ seatsLeft(): intCheckout~ quote(Event): String

After the fix: three small classes, each about one thing, and a Checkout that depends on Event alone.

How one class depends on another

Example

Tightness

Uses its internal details

capacity - attendees.size() in the checkout

Tightest

Shares global data with it

Both read a global seat count

Tight

Tells it how to work

A flag that picks which branch it runs

Medium

Passes it a whole object it barely uses

Passing an Event to format a title

Loose

Passes it just the data it needs

quote(title, seatsLeft)

Loosest

6 · Spotting them

Signals, not targets

Both can be estimated by reading code:

  • Cohesion: do a class's methods use the same fields? Methods that share no fields with each other, like the venue, attendance and sponsor groups above, are separate jobs. Tools count such method pairs (the LCOM family of metrics).
  • Coupling: how many other classes does a class use, and how many of their members? A class using four of another's getters to compute something is coupled to its internals.

After the fix: the checkout talks only to the event, and only the event knows about the venue.

Treat the numbers as signals, not goals. Zero coupling means classes that don't work together at all, and one-method classes have perfect cohesion and no use. The aim is coupling that's deliberate and narrow, and classes whose parts change together.

7 · In practice

What the checks look for

Several practice checks are coupling and cohesion in disguise: a class with many methods and many collaborators (a god class), a method that mostly uses another class's data (feature envy), and classes that depend on each other in a loop. The principle pages that follow, starting with Single responsibility principle, each sharpen one part of this idea.

Design the parking lot and keep its classes focused.

Check yourself

4 questions

1. Event's venue methods, attendance methods and sponsor methods share no fields. What does that show?
2. Why is capacity - attendees.size() in the checkout a coupling problem?
3. Pulling Venue out of Event and adding Event.seatsLeft() changed which measures?
4. Should a design aim for zero coupling?

Takeaways

Remember this

  • Cohesion: how well the parts inside a class belong together.
  • Coupling: how much a class depends on others, and how deeply.
  • Methods that share no fields are a sign of low cohesion: separate jobs in one class.
  • Depending on another class's internals is the tightest coupling; ask it for an answer instead.
  • Aim for high cohesion and loose coupling; treat metrics as signals, not targets.