The picture above illustrates our current design.
Notice that the switches we've put into our business logic do not matter here.
Since any component can potentially show up for each group the connection needs to be there.
This means that, for instance, the ManageFeatureToggles
component has an afferent coupling value of 4 (because its used inside the UserProfile
component but also indirectly in ProductAdmin
, SupportAdmin
, and MarketingAdmin
).
If we make a change in that component we can potentially affect four others.
Also, the UserProfile
has an efferent coupling value of 3 since it uses three components (ManageFeatureToggles
, ManageTracking
, and DisableUser
).
A change in any of the three components could have an effect on the user profile itself.
Again, none of these values are inherently good or bad.
But they give us something that we can compare against.
Let's look at a different way to structure the code.
This time we're going to build dedicated components for each kind of user profile.
In this design, the ProductProfile
has an afferent and efferent coupling value of 1.
If we anticipate more changes to the product profile then this design might be more favorable.
Why?
Because changes are local to the product use case and cannot influence other parts of our application.
We have also introduced a new UserDetails
component.
This component contains the code that we actually intended to re-use.
I would argue that extracting the user details into a small component is the better way to structure our code in this scenario.
The new component is small and has no switches which means that all the code (the user information) adds value in every use case where it is rendered.
UserDetails
has an afferent coupling value of 6 which isn't bad because we've designed this component to be re-used.
Update 2020-09-01 : It got pointed out to me that it might be helpful to include the changed code for the specific profile components as well.
Here we go.
What you will end up with are more but smaller components without conditionals in them.
Product profile< > >
import ManageFeatureToggles from "./ManageFeatureToggles"
import UserDetails from "./UserDetails"
function ProductProfile ( { user } ) {
return (
< >
< UserDetails user = { user} />
< ManageFeatureToggles user = { user} />
</ >
)
}
Support profile< > >
import DisableUser from "./DisableUser"
import UserDetails from "./UserDetails"
function SupportProfile ( { user } ) {
return (
< >
< UserDetails user = { user} />
< DisableUser user = { user} />
</ >
)
}
Marketing profile< > >
import ManageTracking from "./ManageTracking"
import UserDetails from "./UserDetails"
function MarketingProfile ( { user } ) {
return (
< >
< UserDetails user = { user} />
< ManageTracking user = { user} />
</ >
)
}
What does this have to do with cohesion ?
In the first design changes to one use case (e.g. product) would have an effect on other use cases (e.g. marketing).
This was reflected in high afferent coupling values on components where we would have expected low values.
Since these use cases should not overlap but the components are connected I would argue that we did not achieve cohesion because two components that do not share a use case might influence each other.
The second design approach is different.
If we wanted to add another component to the product profile we could implement that without affecting, for instance, any marketing component.
This means that you can achieve cohesion by making sure that components that serve a specific use case have an afferent coupling value which represents this.
If you have three components that handle product use-cases but one of those has an afferent coupling value of 10 then this is a smell that you have not achieved cohesion.