Skip to main content

Coupling and Cohesion

Every module should mind its own business tightly, and mind everyone else's loosely.

Two measurements, and the whole pattern catalog spends its life trying to improve both at once. Cohesion asks how well a module's own responsibilities belong together - a class whose methods all touch the same fields for the same reason is highly cohesive. Coupling asks how much one module has to know about another to work - two classes that share only a narrow interface are loosely coupled; two classes that reach into each other's fields are tightly coupled. The target has a name: high cohesion, low coupling.

Why it exists

Neither measurement is optional cosmetics - both predict how expensive a future change will be. Low cohesion means one class's methods pull in different directions, so a change meant for one job risks breaking an unrelated one living in the same file. High coupling means a change to one class ripples into every class that reached too far into it. The two usually move together: shrinking a class down to one cohesive job is typically what gives it a narrow enough interface to loosen its coupling to everyone else.

A class doing two unrelated jobs, split

// UserManager: two jobs, one class, one shared field list.
class UserManager is
field db: Database
field smtpClient: SmtpClient
 
method createUser(email, password) is
if password.length() < 8 then
throw new WeakPasswordError()
userId = db.insert("users", email, hash(password))
smtpClient.send(email, "Welcome!", buildWelcomeBody(email))
return userId
 
// Password rules and email delivery share nothing except this
// class. A change to either drags the other one's tests along.
// Two collaborators, each with one job, held by reference.
class PasswordPolicy is
method validate(password) is
if password.length() < 8 then
throw new WeakPasswordError()
 
class WelcomeMailer is
field smtpClient: SmtpClient
 
method send(email) is
smtpClient.send(email, "Welcome!", buildWelcomeBody(email))
 
class UserManager is
field db: Database
field passwordPolicy: PasswordPolicy
field mailer: WelcomeMailer
 
method createUser(email, password) is
passwordPolicy.validate(password)
userId = db.insert("users", email, hash(password))
mailer.send(email)
return userId
 
// UserManager is now loosely coupled to both: one call each,
// no shared internals. Each collaborator is highly cohesive:
// everything in it is about exactly one job.

UserManager above went from low cohesion (password rules and email delivery sharing one class) to holding two loosely coupled collaborators, each highly cohesive on its own. The same move happens twice more elsewhere in this module: Encapsulate What Varies pulls tax calculation out of Order into a cohesive TaxCalculator, and Separation of Concerns splits OrderImportJob into four collaborators for the same reason.

Try it yourself: PasswordPolicy and WelcomeMailer above still both get constructed and held directly by UserManager. Is that still tight coupling in a smaller way? Compare your answer against Dependency Injection once you've read it.

The cost of overapplying it

Coupling can be driven toward zero by inserting an interface between every two classes that ever call each other, and cohesion can be driven toward "perfect" by giving every method its own single-method class. Taken that far, a system that used to require reading three files to trace one intent now requires reading fifteen, each one nearly empty, connected by indirection that exists for the metric rather than for any real flexibility being used. The two dials are means to an end - cheaper future changes - not scores to maximize for their own sake.

How it relates to its neighbours

Law of Demeter is a coupling failure caught in one specific, grep-able shape: a chain of dots reaching through one object into another's internals. A class can be perfectly cohesive and still badly coupled if it hands its internal state to anyone who asks

  • cohesion and coupling stay two separate dials for exactly that reason.

Where you'll see it in the pattern catalog

Facade and Adapter exist to manage coupling directly, by giving a caller one narrow interface instead of several wide ones. Mediator reduces coupling among peers by routing their communication through a hub instead of a many-to-many web. And in this site's additional patterns, Dependency Injection is the mechanical move that keeps a class's coupling to its collaborators down to "knows one interface," never "knows how to build the concrete thing behind it."

Check yourself

Question 1 of 3

What is the actual target when people say "high cohesion, low coupling"?