Technical Practices: High Cohesion

I see High Cohesion as a key component of getting single responsibility into code. Hopefully it's clear from my blogging that there's far more to it than just one thing, but a class that is does not have High Cohesion cannot have a single responsibility.

Why is that?

A class that has a single responsibility has collaborators that all work together to produce the desired behavior(s).
This is high cohesion to me

The collaborators of a class all work together to perform the desired behavior(s).

When collaborators exist for only part of the behaviors the class encapsulates, they are not cohesive to the rest of the class' collaborators. Which means the class is not as highly cohesive as it can be.

This is a key point of refactorability. We have collaborators that are not cohesive with The rest of the collaborators. Can the interactions it DOES have be encapsulated in a new object? If so, this will create an object that is highly cohesive, since we're extracting out collaborators that are highly cohesive.

Now we have a highly cohesive class.

Continue to refactor out higly cohesive classes.

From what you start with, you'll likely end up with a class that coordinates the interaction between highly cohesive classes. Which makes it highly cohesive.

We're getting highly cohesive classes by extracting high cohesion collaborators.

What next?

If you have no getters - you do have no getters... right?! - then you'll have encapsulated the behavior around data into an object with data. When you extract the highly cohesive collaborators into new objects, they get the associated behavior. It continues to shove behavior into object until you pretty much get "single behavior objects".
I consider microObjects a form of extreme refactoring.

When do you stop refactoring?
Until you can't refactor.

Even if you don't go that far. The code will want to be encapsulated into smaller and smaller responsibilities. You can stop sooner than single behavior - but what reasons will the class change?

The next thing to do when refactoring is to extract all the highly cohesive objects into classes for the behaviors they are cohesive around.

Don't Do - Give

There's two ways I look at how to handle the new objects from extracting highly cohesive objects around a behavior. Your class can essentially delegate and call into the highly cohesive class, maybe passing in a required (but not as highly cohesive) collaborator. This works. It's often easier to start this way.

I like to return a new object. Pass in everything it'll need in the constructor - be immutable - and the caller will have a new object with defined behavior to interact with.

This can start a bit of a train-wreck appearance in the code. I don't deny that - but I do reject that it IS a train-wreck. We're not using getters. We're not touching class references. We are ONLY interacting with behaviors from all the objects we have to touch.
Highly cohesive objects, that implement a contract. Each class gets to be as ignorant as we can make it, while still having it do something.

Shutting up now

No code here today. Just some thoughts about extracting highly cohesive collaborators. It's taken a while to actually get to high cohesion given that it's always been a "small" post in my head. It's a technical practice that's critical to drive code to microobjects, and just good OO code in general.

Show Comments