Software Architecture & Design Principles
Coupling & Cohesion
Cohesion measures how tightly a module's own responsibilities belong together; coupling measures how tightly it is bound to other modules. Every durable design heuristic — SRP, layering, component boundaries — is really an argument for raising one and lowering the other.
- High cohesion: everything inside a module serves one purpose and changes together
- Low coupling: modules interact through narrow, stable interfaces and know little about each other's internals
- Coupling spectrum (tightest → loosest): content, common, control, stamp, data
- Cohesion spectrum (weakest → strongest): coincidental, logical, temporal, procedural, communicational, functional
- Afferent (Ca) / efferent (Ce) coupling counts and instability I = Ce / (Ca + Ce) make coupling measurable, not just a feeling
| Kind | What's shared | Why it's costly |
|---|---|---|
| Content | One module reaches into another's internals directly | Any internal change breaks the caller — no encapsulation at all |
| Common | Modules share global/mutable state | Hidden channel of influence; order-of-execution bugs |
| Control | One module passes a flag telling another how to behave | Caller must know callee's internal logic to pick the flag |
| Stamp | A whole object passed when only a few fields are used | Caller depends on fields it doesn't need — any of them changing risks breakage |
| Data | Only primitive/simple data passed, exactly what's needed | The loosest practical form — the goal to aim for |
Cohesion is the mirror image: a class with functional cohesion does one well-defined thing end to end (a TaxCalculator); one with coincidental cohesion bundles unrelated methods that happen to live in the same file (a Utils class with string helpers, date helpers, and network helpers). Low cohesion doesn't crash anything — it just means the class has no reason to exist as a unit, and every change to it risks disturbing unrelated callers.
// Stamp coupling: whole Customer passed, only the email is used —
// this method now depends on every field Customer has.
void sendReceipt(Customer customer, Order order) {
mailer.send(customer.getEmail(), order.toReceiptText());
}
// Data coupling: depends only on the data it actually needs.
void sendReceipt(String recipientEmail, String receiptText) {
mailer.send(recipientEmail, receiptText);
}