Skip to content
Part IA Michaelmas Term

Coupling

Coupling measures how much one class depends on the internal details of another.

Tight Coupling — The Problem

Tight coupling means class A reaches directly into B’s internals or depends on a concrete implementation class rather than an interface. A change to B risks breaking A.

class ReportGenerator {
    private MySQLDatabase db = new MySQLDatabase();  // tightly coupled to MySQL

    void generate() {
        ResultSet rs = db.executeQuery("SELECT * FROM sales");
        // ...
    }
}

If you switch to PostgreSQL, or want to test ReportGenerator without a real database, you must modify ReportGenerator’s source code. The class is tightly coupled to MySQLDatabase.

Signs of Tight Coupling

  • Using new on concrete classes deep inside methods (hard-coded dependencies).
  • Accessing another class’s fields directly (obj.field rather than obj.getField()).
  • Long method chains: a.getB().getC().doSomething() — the caller knows about and depends on the entire structure of the object graph (a Law of Demeter violation).
  • Methods that take or return concrete implementation types rather than interfaces.

Loose Coupling — The Goal

Loose coupling means classes communicate through small, stable interfaces. Concrete implementations can be swapped freely.

class ReportGenerator {
    private Database db;  // depends on the interface, not a concrete class

    ReportGenerator(Database db) { this.db = db; }

    void generate() {
        ResultSet rs = db.executeQuery("SELECT * FROM sales");
        // ...
    }
}

interface Database {
    ResultSet executeQuery(String sql);
}

Now ReportGenerator works with any Database implementation — MySQLDatabase, PostgresDatabase, or a mock FakeDatabase for testing. Changing the database technology requires zero changes to ReportGenerator.

Benefits of Loose Coupling

  1. Open/Closed Principle: the system is open for extension (new Database implementations) but closed for modification (no changes to ReportGenerator).
  2. Testability: you can inject mock objects for unit testing (new ReportGenerator(new MockDatabase())).
  3. Reusability: ReportGenerator can be reused in a different context with a different database implementation.
  4. Parallel development: one team works on ReportGenerator, another on PostgresDatabase — they only need to agree on the Database interface.

Reducing Coupling — Techniques

1. Depend on Interfaces, Not Implementations

List<String> list = new ArrayList<>();  // good — declared as List, not ArrayList

2. Dependency Injection

Pass dependencies in through the constructor (as in the ReportGenerator example above). Avoid creating dependencies inside methods with new. There are frameworks (Spring, Guice, Dagger) that automate dependency injection, but the principle is independent of any framework.

3. Keep Method Parameter Types as General as Possible

void process(Collection<String> items) { ... }  // accepts any Collection

not

void process(ArrayList<String> items) { ... }  // unnecessarily specific

4. Avoid Exposing Internal Data Structures

Use getters that return unmodifiable views or copies, not the raw internal collection:

class Team {
    private List<String> members = new ArrayList<>();

    List<String> getMembers() {
        return members;  // bad — caller can modify the internal list
    }

    List<String> getMembers() {
        return Collections.unmodifiableList(members);  // good — safe read-only view
    }

    List<String> getMembers() {
        return new ArrayList<>(members);  // good — defensive copy
    }
}

Coupling versus Cohesion

These two concepts are complementary:

  • High cohesion: a class has a single, well-defined responsibility. All its fields and methods work together towards that purpose. A class with high cohesion is easier to understand, maintain, and reuse.
  • Low coupling: a class depends on few other classes, and those dependencies are through stable interfaces. Low coupling isolates the effects of changes.

A well-designed system has high cohesion within classes and low coupling between classes. They reinforce each other: when each class has a clear, focused responsibility, it naturally has fewer reasons to depend on other classes.

The Law of Demeter (Principle of Least Knowledge)

A method m of class C should only call methods of:

  • C itself
  • Objects created by m
  • Objects passed as parameters to m
  • Objects held in instance variables of C

It should not call methods on objects obtained by chaining through intermediate objects:

account.getOwner().getAddress().getPostcode();  // Demeter violation

Each dot after the first peels back a layer of implementation detail that the calling code has no business knowing. If the structure of Account or Owner changes, this line breaks. Instead, expose a focused method:

account.getOwnerPostcode();  // encapsulates the chain internally

Coupling as a Spectrum

Coupling is not binary — it exists on a spectrum. Zero coupling is impossible (classes must interact to form a working program). The goal is to keep coupling as low as practical, and to ensure that whatever coupling exists is through stable abstractions (interfaces, abstract classes) rather than volatile concretions (concrete classes whose details change frequently).