Cohesion is a measure of how strongly a software unit's elements are related. In a highly cohesive system, the functionality offered by components in the same module is strongly related. It feels like such components just belong together.
On a class level, the more fields a method manipulates, the more cohesive it is to the class. This means that the most commonly spotted low-cohesion data types are those big monolithic ones. When there's too much going on in a class, it most probably is not cohesive and breaks the SRP, too. This makes such classes hard to maintain and bug-prone.
Smaller classes can be incohesive as well. Consider the following example. It may seem trivial, but posting real-life scenarios, often hundreds if not thousands of lines long, would be impractical:
class CachingProcessor {
public:
Result process(WorkItem work);
Results processBatch(WorkBatch batch);
void addListener(const Listener &listener);
void removeListener(const Listener &listener);
private:
void addToCache(const WorkItem &work, const Result &result);
void findInCache(const WorkItem &work);
void limitCacheSize(std::size_t size);
void notifyListeners(const Result &result);
// ...
};
We can see that our processor actually does three types of work: the actual work, the caching of the results, and managing listeners. A common way to increase cohesion in such scenarios is to extract a class or even multiple ones:
class WorkResultsCache {
public:
void addToCache(const WorkItem &work, const Result &result);
void findInCache(const WorkItem &work);
void limitCacheSize(std::size_t size);
private:
// ...
};
class ResultNotifier {
public:
void addListener(const Listener &listener);
void removeListener(const Listener &listener);
void notify(const Result &result);
private:
// ...
};
class CachingProcessor {
public:
explicit CachingProcessor(ResultNotifier ¬ifier);
Result process(WorkItem work);
Results processBatch(WorkBatch batch);
private:
WorkResultsCache cache_;
ResultNotifier notifier_;
// ...
};
Now each part is done by a separate, cohesive entity. Reusing them is now possible without much hassle. Even making them a template class should require little work. Last but not least, testing such classes should be easier as well.
Putting this on a component or system level is straightforward – each component, service, and system you design should be concise and focus on doing one thing and doing it right:
Low cohesion and high coupling are usually associated with software that's difficult to test, reuse, maintain, or even understand, so it lacks many of the quality attributes usually desired to have in software.
Those terms often go together because often one trait influences the other, regardless of whether the unit we talk about is a function, class, library, service, or even a whole system. To give an example, usually, monoliths are highly coupled and low cohesive, while distributed services tend to be at the other end of the spectrum.
This concludes our introductory chapter. Let's now summarize what we've learned.