What Are Cohesion and Coupling?
Cohesion and coupling address when things should and should not be too sticky with one another
Introduction
This blog was originally going to be a continuation of the previous Abstract blog where I planned to follow up with Cohesive Abstractions.
I was close to publishing it, but I realized that I was devoting almost 1,000 words to Cohesion and Coupling, which started to feel like its own separate blog. I’m making a last-minute decision to blog exclusively on Cohesion and Coupling first, and then follow up with Cohesive Abstractions shortly thereafter.
Cohesion and Coupling
Cohesion and Coupling both deal with the nature of how software elements are connected. These aren’t always the easiest concepts to understand, especially when considered together.
The former is about elements that should be connected but often aren’t connected. The latter is about elements that should not be connected but often are connected.
Cohesion
It took me a while before I had a solid understanding of Cohesion.
Elements are cohesive when there’s an intrinsic relationship that connects them. The relationship is often gestalt in that the elements only have meaningful utility when combined as a whole. When an update is needed, most or all elements in the cohesive relationship will require updating.
High Cohesion
The intrinsic relationship between screw and screwdriver is an example. Neither is functional without the other. The screwdriver must also match the type of screw. For example, a flathead screwdriver won’t work on a Phillips head screw. If the design changes from a flathead screw to a Phillips head screw, then the screwdriver will need to change from a flathead to a Phillips head likewise.
Nuts and bolts are cohesive. They only function when screwed together, and when their sizes match.
We want our designs to have high cohesion. That is, software elements with intrinsic relationships should be near one another in the design.
Low Cohesion
Low cohesion occurs when related software elements have been distributed across the design. For example, you’re enhancing an existing behavior, and it requires changes to a dozen files distributed throughout the design. Additionally, the files being updated also contain code that’s not directly related to the behavior that you’re updating.
Low cohesion has at least two concerns:
- Did you update all the files that need to be updated?
- What if your update breaks the other content that’s not directly related to the behavior you’re updating?
A common example of low cohesion I’ve seen is state machine behavior (blog TBD) spread across the implementation.
This usually happens when a class has a private
attribute named something like status
or state
.
Its type is often an enum
, but I’ve also seen it as a boolean
and even a String
. Its implied privacy is violated with get
and set
accessors, which provide direct access to the private
attribute.
Rather than keeping the state machine behavior encapsulated within the class, the class becomes the place where state
or status
reside. Other classes will access status
/state
via the get
accessor, update status
/state
with a new value via the set
accessor based upon their own business logic code. Not only does this low cohesive design distribute the state machine implementation across far flung regions of the design, it distributes the ability to understand the behavior. It’s like being Robert Langdon in one of Dan Brown’s novels running around desperately looking for clues to see the bigger picture. This makes it more difficult to know what the state machine will do as a whole when the status
/state
is updated from any place in the implementation.
With cohesive software elements, a change to one element may require a change to the other related cohesive elements, like how a change in screw head causing a change in the screwdriver. Highly cohesive software elements are close to one another, such as in the same package, making it more likely that all required updates will occur consistently.
Interface Cohesion
I consider the methods of an interface
cohesive when:
- The methods are functionally related and support each other.
- The removal of one of the methods diminishes the whole.
Traditional Create, Read, Update and Delete (CRUD) operations are cohesive in my mind. Remove any of them, and the remaining operations are incomplete. The requirements for one of my features contained Create, Update and Delete requirements for a set of domain elements, but there were no Read requirements. I asked my requirements creator why I was implementing features to manage data when there were no requirements to read the data. Oops.
A cohesive interface tends to follow the Interface Segregation Principle (ISP). An interface that violates ISP contains methods that can be separated into their own cohesive interfaces.
Coupling
Coupling is often uttered in the same breath with cohesion. It took me a while to gain a solid understanding of coupling, especially when used with cohesion. They seemed similar yet different, and I wasn’t sure how to distinguish them.
Coupling is also about connected elements. However, unlike cohesive elements, coupled elements do not have intrinsic relationships.
A Reese’s Peanut Butter Cup couples chocolate and peanut butter together. I know for many, a peanut butter cup would be an excellent example of cohesion, but I’m the odd duck who doesn’t like peanut butter cups, even though I like chocolate and peanut butter separately.
We want our designs to have loose coupling. That is, we don’t want unrelated elements to be stuck together too tightly.
A Reese’s Cup would be tightly coupled if it were the only form from which we could get chocolate or peanut butter. Imagine scraping the insides from a Reese’s Cup as the only means to make a peanut butter sandwich.
Tight Coupling
Tight coupling occurs when disparate software concepts are stuck together. For example, the communication framework, business logic and persistence implementations are intertwined in the same method. A change to one of these elements runs a risk of affecting or breaking one of the other non-related coupled elements. A tightly coupled method is often a violation of the Single Responsibility Principle, since there’s more than reason for it to change. Reusing the business logic in another context becomes impossible when it’s tightly coupled with other elements.
Loose Coupling
Many modern software practices, such as Design Patterns, Hexagonal Architecture/Ports & Adapters, and others address tight coupling. They promote loose coupling. The first Design Pattern Principle addresses this, even if it doesn’t mention coupling directly: Program to an interface, not an implementation.
Summary
A good design features high cohesion for those software elements that do change together and loose coupling of software elements that don’t change together.
Without care and consideration, a design can easily degrade into low cohesion and tight coupling, as described above, which are undesirable design traits.
The two pairs of high/loose and low/tight tend to go together. If a design has high cohesion, then it tends to have loose coupling and vice versa. The same applies to the low cohesion/tight coupling pair.
References
- Cohesion via Wikipedia
- Coupling via Wikipedia
- Difference Between Cohesion and Coupling via Tutorialspoint
- Difference Between Cohesion And Coupling Explained (With Examples) via Unstop
- Coupling and Cohesion in Software Engineering by Mitali Varatiya
- Difference Between Coupling and Cohesion by Anshuman Singh
- Coupling and Cohesion Explained video by Gui Ferreira
- SOLID Principles? Nope, just Coupling and Cohesion video by CodeOpinion
- Highly COHESIVE Software Design to tame Complexity video by CodeOpinion
- Loose vs Tight Coupling video by Scott Bailey
- Loose & Tight Coupling: Why Code is Hard to Change video by Bran van der Meer
- and for more, Google: cohesion vs coupling in software engineering