Home

High Cohesion and Low Coupling Outside of OOP (With Examples)

If you have been programming long enough, you have probably heard someone mention High Cohesion and Low Coupling at one point or another.

Although this subject is often mentioned in the context of Object Oriented Programming, it is actually a concept applicable to other paradigms and computer science as a whole.

But what does it mean exactly?

Cohesion

Cohesion here means the degree to which elements that logically belong together are actually together when expressed in code.

In other words, have you structured your code in logical, related, cohesive blocks?

Note that the word blocks here can mean many things. In OOP this is typically classes, but in different contexts can also mean functions, files, modules, etc…

When your code is cohesive, it can be easier to understand, find and modify a specific aspect of it.

Coupling

The standard way of describing coupling is to say: “It’s the degree to which your software entities depend on one another”.

Easy right? Nope. While the description of coupling can be straight forward to some, in practice people tend to misunderstand it.

It is important to realize that low coupled (same as de-coupled) code does not mean “code that does not depend on other code”.

Instead, low coupled code is the one that carefully considered it’s boundaries and API at a design level.

When your code has low coupling, it becomes reasonable to completely change larger portions of the application without breaking unrelated functionality.

Broadly speaking, low coupled has very little leaky abstractions. When refactoring low coupled code, there should be no breakage - as long as the API’s remain the same and the new logic is sound.

Practical Examples

The above descriptions are decent, but they may not mean much to newcomers. Bellow are some fast and furious examples to help ilustrate the concept outside of an OOP context.

Low Cohesion & High Coupling

The easiest example of Low Cohesion & High Coupling in the real world is this:

<div class="post">
  <h1 class="title" style="color:red;">
    This post title is red
  </h1>
</div>
<div class="post">
  <h1 class="title">
    This post title is red
  </h1>
</div>

styles in plain HTML

The above is not cohesive because in HTML we describe what we have in the page, not how it looks. The place for styles would be a separate .css file.

It’s tighly coupled because the choice of a restrictive API (style="") creates a strong one-to-one relation where the styles cannot be re-used in any other elements.

High Cohesion & High Coupling

import React from 'react';
import { render } from 'react-dom';
import { createUseStyles } from 'react-jss';

const useStyles = createUseStyles({
  myButton: {
    color: 'green',
    margin: {
      top: 5,
      right: 0,
      bottom: 0,
      left: '1rem'
    },
    '& span': {
      fontWeight: 'bold'
    }
  },
  myLabel: {
    fontStyle: 'italic'
  }
});

const Button = ({ children }) => {
  const classes = useStyles();
  return (
    <button className={classes.myButton}>
      <span className={classes.myLabel}>{children}</span>
    </button>
  );
};

const App = () => <Button>Submit</Button>;

render(<App />, document.getElementById('root'));

styles in React's JSX

Although similar to the previous example, usage of CSS in JS is quite common nowadays. You can learn more about it here.

Even though the above code still tightly couples the styles to the component, it is cohesive. This is because React’s separation of concerns revolves around segregating code based on components and their functionality instead of the regular HTML/CSS/JS paradigm.

Contrast this example with the previous, and you have a clear picture of how the entire High Cohesion and Low Coupling concept is measured in a qualitative fashion - the concept is to be applied to the overall design of your solutions.

Low Cohesion & Low Coupling

Consider the following code:

forms = [
  { formContent: '1 Lorem Ipsum 1', state: 'APPROVED' },
  { formContent: '2 Lorem Ipsum 2', state: 'REJECTED' },
  { formContent: '3 Lorem Ipsum 3', state: 'UNDER_REVIEW' },
  { formContent: '4 Lorem Ipsum 4', state: 'DRAFT' }
];

function getApprovedForms() {
  return forms.filter(f => f.state === 'APPROVED');
}

function getRejectedForms() {
  return forms.filter(f => f.state === 'REJECTED');
}

function getUnderReviewForms() {
  return forms.filter(f => f.state === 'UNDER_REVIEW');
}

function getDraftForms() {
  return forms.filter(f => f.state === 'DRAFT');
}

While the above code has low coupling in the sense that each function implementation is completely independent from the others, it is an example of low cohesion since closely related logic (in this case - repeated logic!) is scattered repeatedly across multiple functions.

While the above example is extremely simple, in the real world this type of anti-pattern tends to manifest itself in the very problematic ways:

  1. It usually spreads across many files.
  2. Functions are not named similarly.
  3. Small differences in implementation make it difficult to find and replace all instances of duplicate logic.

High Cohesion & Low Coupling

The more cohesive version of the above would seek to centralize closely related logic. In this case this would mean eliminate duplicate code:

forms = [
  { formContent: '1 Lorem Ipsum 1', state: 'APPROVED' },
  { formContent: '2 Lorem Ipsum 2', state: 'REJECTED' },
  { formContent: '3 Lorem Ipsum 3', state: 'UNDER_REVIEW' },
  { formContent: '4 Lorem Ipsum 4', state: 'DRAFT' }
];

function getFormsByState(state) {
  return forms.filter(f => f.state === state);
}

Conclusion

Many seemingly arbitrary best practices form themselves based on larger ideas like high cohesion & low coupling, SOLID principles and etc.

Although some of these concepts are usually discussed in an OOP context, they are often broad enough to be applicable in different programming paradigms.