Cohesion for objects is how well the behaviors in the class belong together. Some very non-cohesive behaviors would be multiplying two numbers and splitting a string.
Multiplying and Raising to a power can be very cohesive behaviors.
How do µObjects do cohesion? That's what this is about. The simplest is - They only have one behavior. Let's dig into a little detail.
µObjects are single purpose. Intrinsicly highly cohesive class
µObjects strive to do A thing. Not a job, not a responsibility - They want to do ONE thing. As I've mentioned elsewhere (no idea which post); µObjects are "Single Responsibility to the Extreme."
This isn't meant to always hold 100% the case. I'm finding that µObjects have emergent objects. More complex objects that have a job, or a responsibility, emerge. I don't know how to define this exactly; but I theorize when the Envy smells start to appear (Feature Envy and Data Envy). Another I look at is if the constructor is instantiating many objects, and they can't be broken into tighter couplings. This implies that the class has cohesive behavior.
µObjects combine to build complex behavior through simple functionality
Each µObject does a simple thing. Even the ermerging objects do very simple things. They must prove they will remain cohesive before they can take on additional actions. Method lengths strive to be a single line with Cyclomatic Complexity of 1. The single line and C-Complexity of 1 combined helps drive and maintain the simplicity of the µObject.
The single line is really nice in C# with expression bodies.
With these extremely single responsibility objects you have very simple functionality. You then combine these to build complex behavior.
A favorite example of mine is from work and calling a service that requires authentication. We auth against a different server and use JWT payload to authenticate. If the token expires, we go back and refresh with the auth server, then hit the service again.
This isn't a class or a method that does this. It's about a dozen that are actually involved. I won't get into the task of each class; but if you did a flow chart; each decision and action tends to become an object. It's fantastic to look at all these simple behaviors combining to build a complex and useful product.
µObjects are not as simple to trace through; that's a procedural programming safety blanket
When you follow the flow through µObjects, you're going through a lot of classes. That's the nature of Object Oriented Programming. The idea that you can follow the process in a single method is a safety blanket; a crutch from Procedural Programming.
µObjects give you easy to understand operations that can easily be identified as the point to start debugging. Or the point where a decision is(n't) being made.
A large part of this (which isn't called out explicitly in the headers) is µObject immutability. A class isn't changing it's state. It is as it was constructred (usually).
The simplicity and cohesiveness of each individual object allows its role and behavior to quickly and easily be understood.
When you're debugging, you don't have to keep an eye on the state of objects; they don't change. Procedural code has mutable objects that you have to investigate everytime the code comes back into it... Procedural code is a deceptive safety blanket. As soon as you move away from simple; it becomes much harder to follow.
µObjects allow you to ignore the details of "how" until you need to know
An important aspect to highly cohesive classes is that they get to be dumb. They are ignorant. A µObject doesn't know; nor care; how something else is accomplished. If a class wants to refresh the auth token, it has an instantiated RefreshAuthToken
class and calls refresh
. How it does it - Does. Not. Matter.
Looking through the code, following the process; it can skipped over simplty. We don't have to be aware of the 'how' until, and unless, we want to. The amount of "not code I care about" is dropped to almost zero.
µObjects encourage composition
Inheritance has baggage. It exposes methods and behavior you may not want. Composition allows explicit control over what can be done and how.
Composition helps maintain high cohesion by preventing any behavior not explicitly granted from being available. Inheritance always runs the risk of new functionality being added, and used, that the object shouldn't expose.
I am actually working to have this better understood at work on a couple classes. A URI generator doesn't need a method to get back a string.
µObject Emerged Objects better represents the domain
µObjects as "Single Responsibility to the Extreme" don't represent the domain. Not really. Representing a domain requires more advanced behavior. This requirement and where it belongs shows itself through µObjects emergent Object development.
When you start to get behavior that uses data from a class and somehow manipulates it; maybe that should go inside the object. Let it do the operation for you.
This encapsulation of behavior starts to produce objects with a job. More than A SINGLE responsibility.
These become your domain objects. They don't get additional behavior until it's demonstrated in the code.
An example of this is formatting a telephone number for display. We get 10 digits from the server, eg 5551235678. When we got this value (not a databag, details unimportant) we'd then format it and put it into the control for display as (555) 123-5678
.
We'd also need to create a link of callto:5551235678
. This is taking data and operating on it. It didn't belong in the "Employee" (not actually employee, but who cares) class. What's an employee know about formatting phone numbers? But it drove the creation of a PhoneNumber object. This object knows how to format for display and create a call link. This was an example of an object that emerged from behaviors that manifested in the code and idenitified as wanting to be cohesive.
µObjects Emerged Domain Object representation is through composition
With the above example of the phone number domain object emerging from implemented behaviors; we need to build this behavior from simplier parts. It can't know about the formatting or about the call link itself. These behaviors belong to other objects. The PhoneNumber
uses them through composition. formatting is done as a class; It knows about the phone number format. A different class knows about the call link format.
A lot of this comes back to single responsibility as a single reason to change. If we change the format of a phone number; what class changes? PhoneNumber
? It will also have to change if we change the format of the call link. We need a PhoneNumberFormat
class. In UWP land; we're provided one (which we wrap as per the OSAL). We built a custom one for the call link.
This is a simple example of how we continue to use µObjects that do a single task to build complex behavior through composition.
Our phone number object is very maintainable because all it actually knows how to do, is call things to transform the phone number it holds.
Summary
µObjects are highly cohesive. They don't do more than one thing until it's shown, by having written some code, that they should have more behavior. Most of the time this cohesion is represented by composition.
The behaviors that are cohesive are still delegated and performed by µObjects that do that single tasks. The domain class takes on the single responsibility of knowing what is required to execute the behaviors.