Coupling refers “to the strength of a connection between two routines or classes. Coupling is a complement to cohesion. Cohesion describes how strongly the internal contents of a routine or class are related to each other. Coupling describes how strongly a routine is related to other routines. The goal is to create routines and classes with internal integrity (strong cohesion) and small, direct, visible, and flexible relations to other routines and classes (loose coupling).”
Every time you create a connection between two points in a software system, you incur the risk that a change to one of those things will affect the other. Rampant, uncontrolled coupling drives the risk upward until it reaches the point of being a relative certainty that every change will have side-effects to which a developer must attend. At some point, the question transforms from “Will there be an unexpected side effect to this change?” to “How far reaching will this change need to be?” and the “ripple effect” is born.
Controlling coupling helps you limit that ripple effect.
We cannot simply eradicate all coupling from a software system, though, because coupling is required to make a software system work. If nothing can talk to anything else, then nothing will ever get done.
You also can’t treat coupling as a quantity, with “a lot” being bad and “a little” being good. If a software system has a lot of coupling but it is all required in order to solve a necessary problem, then there isn’t a coupling problem. If a software system has a little coupling but virtually none of it is required, then there is a coupling problem.
Instead of mere amount or ambiguous “tightness” and “looseness” pseudo-quantities, you need something that embraces the inherently qualitative nature of coupling. You need a way to measure and indicate how much of the coupling in a system is illogical or unnecessary.
The best approximation for necessity is intent. If all the coupling in a system is intentional, created for a logical purpose by a developer, then odds are most of it is necessary. If most of the coupling is accidental, something that simply slipped by without notice, then it is very likely that a system has a lot of unnecessary coupling in it. Also, if coupling was not intentionally created, then it will likely be forgotten about, and the possibility of unintended side-effects becomes much more likely.
But it is not enough to identify whether your coupling is intentional, it is also important to know what kind of coupling it is. There are four kinds of coupling that we primarily concern ourselves with:
- Identity coupling: Coupling to type; one type requires that another type must exist, or the first type will not compile. A good examine is a type-safe container of some collection of another type. The container would have identity coupling to the contained type, but not vice-versa.
- Representational coupling: Coupling to Interface; one type is coupled to the interface of another type. A good example is the client-service relationship. A client class is vulnerable to changes in the interface of the service class.
- Inheritance coupling: Coupling to a base class, or super-type. Changes to base classes can propagate downward to derived classes. This can be a good thing, but only if that was the intent.
- Subclass coupling: Coupling to the polymorphic nature of a service. When a client holds a reference to a type, but in fact it is a sub-type (or an implementing type if the reference is an interface) the question is, does the client know this? If so, then it has subclass coupling. If not, then it does not.
These four types of coupling are often necessary in most modern designs using most modern languages.
A fifth type of coupling is almost never required and it’s pretty safe to assume is always accidental (at least, it should always be accidental). That kind of coupling is implementation coupling: coupling to how something is done or its internal structure.