Cohesive Modules
by kirk knoernschild
Statement
Module behavior should serve a singular purpose.
Description
Cohesion is a measure of how closely related and focused the various responsibilities of a module are. In the worst case scenario, little emphasis is placed on the allocation of classes to modules. Instead, modules are assembled at random, and the likelihood that modules suffer from lack of cohesion is high. In the best case scenario, modules exhibit high degrees of functional cohesion and the entities composing the module each work in conjunction to fulfill module behavior.
In general, cohesion is a qualitative measurement that is difficult to measure. Instead, we typically refer to a software module as either possessing high cohesion or low cohesion. Modules with higher degrees of cohesion are preferred. There are key difficulties that arise when modules suffer from low cohesion, including the inability to understand the software system and difficulty maintaining the software system.
Reusing modules that lack cohesion is also difficult. Module consumers rarely need all of the random behaviors provided by modules lacking cohesion, and the random behaviors often increase dependencies on other modules. In general, modules that lack cohesion degrade the overall quality of a software system’s architecture.
Implementation Variations
There are two key elements that affect module cohesion. These follow:
- The rate at which the software entities within a module change.
- The likelihood that the software entities within a module are reused together.
Based on these statements, it’s easy to draw the following conclusion:
- Classes which change at different rates belong in separate modules.
- Classes that change at the same rate belong in the same module.
- Classes not reused together belong in separate modules.
- Classes reused together belong in the same module.
Unfortunately, this simple logic is flawed because it doesn’t consider the intricate relation between rate of change, reuse, and the natural shifts that occur throughout the development lifecycle. It’s possible, even likely, that classes change at different rates but are reused together, or that classes change at the same rate but are rarely reused together. Indeed, there is a tension between rate of change and reuse, and we must consider the following combinations.
- Classes within a module that change at the same rate and are reused together.
- Classes within a module that change at a different rate and are reused together.
- Classes within a module that change at the same rate and are not reused together.
- Classes within a module that change at a different rate and are not reused together.
The first and fourth scenarios are the easiest to accommodate. When classes change at the same rate and are reused together, it’s logical that they belong in the same module. Likewise, classes that change at different rates and are not reused together belong in separate modules. The final two scenarios, which tend to be most common, are also the most challenging.
Fortunately, the natural shifts that occur throughout the development lifecycle provide insight to dealing with the second and third scenarios, and encourage us to emphasize one of these aspects more than the other. Early in the development lifecycle, when the system is volatile and change is rampant, packaging the classes based on rate of change should supersede packaging based on reuse. As the system stabilizes, we should focus on packaging the classes based on reuse since the rate of change is much less.
Consequences
Early in the development lifecycle, when change is rampant, we are encouraged to package classes into more coarse-grained module. This is logical. Because change is rampant and widespread, packaging into coarse-grained modules means less module management as change occurs. We don’t have to worry as much about managing dependencies between modules. Unfortunately, this can lead to dire consequences if not dealt with on an ongoing basis. Failure to refactor the modules as the system stabilizes results in a software system composed of coarse-grained modules.
The ramifications of coarse-grained modules are discussed in Chapter 6, Section 6.1. Coarse-grained modules make the modules easier to use, but the increased ease of use comes at the price of reusability. It’s vital to recognize that shifts will occur throughout development As the rate of change lessens, it’s imperative to turn our attention toward the cohesive properties of the modules that affect reuse.
Sample
Figure 1 illustrates the initial version of our system, where Bill instances are routed to an appropriate place so they can be handled (ie., rejected or paid). Unfortunately, the bill.jar module lacks cohesion, since the Bill and Router instances are each bundled and deployed in that module. The lack of cohesion affects reuse, as we cannot reuse Bill without the Router nor reuse the Router to handle other types of entites that might require routing.

Figure 1: Module Lacking Cohesion
The code for the Bill class is shown in Listing 1. As can be seen, a Router is passed to the Bill constructor, which is used when the Bill’s route method is called. The Router is an abstract class with an abstract route method on it. The TypeARouter shown in Figure 1 extends the Router and provides an implementation for the route method.
package com.extensiblejava.bill;
import java.math.*;
import com.extensiblejava.route.*;
public class Bill {
private String type;
private String location;
private BigDecimal amount;
private Router router;
public Bill(String type, String location, BigDecimal amount, Router router) {
this.type = type;
this.location = location;
this.amount = amount;
this.router = router;
}
public String getType() {return this.type;}
public String getLocation() {return this.location;}
public BigDecimal getAmount() {return this.amount;}
public String route() { return this.router.route(this); }
}
}
Listing 1: The Bill Class
To increase cohesion of the bill.jar module, routing functionality can be moved to it’s own module, as shown in Figure 2.

Figure 2: Cohesive Bill and Route Modules
The only change required to bundle the functionality separately was to modify the build script. Previously, the build bundled all classes into a single module. The modified script, shown in Listing 2 with the lines responsible for creating the bundles highlight, illustrates how the functionality is allocated to the separate bundles.
<target name="dist" depends="compile">
<jar jarfile="${dist}/bill.jar" basedir="${bin}" includes="com/extensiblejava/bill/**"/>
<jar jarfile="${dist}/route.jar" basedir="${bin}" includes="com/extensiblejava/route/**"/>
<jar jarfile="${dist}/bill-test.jar" basedir="${bin}" includes="com/extensiblejava/test/**"/>
<junit printsummary="yes" haltonfailure="yes">
<classpath>
<pathelement path="${dist}/route.jar"/>
<pathelement path="${dist}/bill.jar"/>
<pathelement path="${dist}/bill-test.jar"/>
<pathelement path="${lib}/junit.jar"/>
</classpath>
<test name="com.extensiblejava.test.RouterTest" outfile="junitresults">
<formatter type="plain"/>
</test>
</junit>
</target>
Listing 2: Modified Build Script
Unfortunately, there is still a glaring problem with the module structure illustrated in Figure 2. A cyclic dependency exists between the bill.jar and route.jar modules. Applying the AcyclicRelationships pattern will help eliminate the cyclic dependency. When applying the AcyclicRelationships pattern to break the cycle between the bill.jar and route.jar modules, we also applied the SeparateAbstractions pattern. Otherwise, the cycle would have persisted. Upon doing so, the final module structure would resemble what’s illustrated in Figure 3. Note the addition of the Routable interface, which is implemented by Bill. It’s the Routable interface, and it’s placement in the route.jar module that eliminates the cycle.

Figure 3: Cohesive Modules with Acyclic Relationships
Wrapping Up
Highly cohesive modules are easier to understand, maintain, and reuse. In many cases though, it can be difficult to create cohesive modules early in the development lifecycle, when the team may not have a clear understanding of system behavior. As this insight is gained, the development team should structure the system to ensure highly cohesive modules are available.