Coupling and Cohesion

The primary OO goals are loose coupling and high cohesion. The key to creating maintainable code is adhering to loose coupling, high cohesion.

Coupling

Coupling in simple words, is how much one component knows about the inner workings or inner components of another one, i.e. how much knowledge it has of the other components.

Components should be as independent as possible from other components, so that changes to component don’t heavily impact other components.

Tight coupling

Tight coupling is where components are so tied to one another, that you cannot possibly change the one without changing the other.

Tight coupling would mean that your module knows the way too much about the inner workings of other modules. Modules that know too much about other modules make changes hard to coordinate and make modules brittle. If Module A knows too much about Module B, changes to the internals of Module B may break functionality in Module A.

public class Foo {

    private int numberOfFoo = 0;

    public int count() {
        if (numberOfFoo == 0) {
            return new Bar().numberOfBars; // tightly coupled
        }
        return numberOfFoo;
    }
    
    public void doSomething1() {
        new Bar().firstCall(); // tightly coupled
    }
}

public class Bar {

    public int numberOfBars;

    public void firstCall() {
    }
}
public class Car  {
    public void move() {
         System.out.println("Moving by car");
    }
}

// Traveler class is tightly coupled with Car class
public class Traveler {
    private Car c = new Car(); 
    
    public void startJourney() {
         c.move();
    }
}

public static void main(String[] args) {
    Traveler traveler = new Traveler();
    traveler.move(); // Moving by car
}

Loose coupling

Loose coupling is a method of interconnecting the components in a system or network so that those components depend on each other to the least extent practically possible.

By aiming for loose coupling, you can easily make changes to the internals of modules without worrying about their impact on other modules in the system. Low coupling also makes it easier to design, write, and test code since our modules are not interdependent on each other. We also get the benefit of easy to reuse and compose-able modules. Problems are also isolated to small, self-contained units of code.

Solutions:

Example of using DI to implement a loosely coupled class:

public class Foo {

    private int numberOfFoo = 0;
    
    /**
     * Using Dependency Injection (DI)
     */
    private Bar bar;
    
    public Foo(Bar bar) {
        this.bar = bar;
    }

    public int count() {
        if (numberOfFoo == 0) {
            return bar.count();
        }
        return numberOfFoo;
    }
    
    public void firstCall() {
        bar.doSomething();
    }
}

public interface Bar {
    int count();
    void doSomething();
}

public class BarImpl implments Bar {

    private int numberOfBars;
    
    @Override
    public int count() {
        return numberOfBars;
    }

    @Override
    public void doSomething() {
    }
}
public interface Vehicle {
      void move();
}

public class Car implements Vehicle {

    @Override
    public void move() {
         System.out.println("Moving by car");
    }
}

public class Traveler {

    /**
     * Using Dependency Injection (DI)
     */
    private Vehicle vehicle;
    
    public Traveler(Vehicle vehicle) {
     this.vehicle = vehicle;
    }

    public void startJourney() {
         vehicle.move();
    }
   
}

// Easy to add new vehicle like Bike
public class Bike implements Vehicle {
    @Override
    public void move() {
         System.out.println("Moving by bike");
    }
}

public static void main(String[] args) {
    Traveler traveler = new Traveler(new Car());
    traveler.move(); // Moving by car
    
    traveler = new Traveler(new Bike());
    traveler.move(); // Moving by bike
}

Cohesion

Cohesion often refers to how the elements of a components belong together. Related code should be close to each other to make it highly cohesive.

There are 2 types of Cohesions: Low Cohesion and High Cohesion.

Low Cohesion

Low cohesion would mean that the code that makes up some functionality is spread out all over your code-base. Not only is it hard to discover what code is related to your module, it is difficult to jump between different modules and keep track of all the code in your head.

If a class has for example three private fields and three methods; when all three methods use just one of the three fields then the class is poorly cohesive.

class FooBar {
  private SomeObject bla = new SomeObject();
  private SomeObject foo = new SomeObject();
  private SomeObject bar = new SomeObject();

  public void domeSomething1() {
    bla.call();
  }

  public void domeSomething2() {
    foo.call();
  }

  public void domeSomething3() {
    bar.call();
  }
}

Example when a class is designed to perform many different tasks instead of focusing on any specific task then that class is called “Low Cohesive class”. This kind of approach is the bad programming design approach. It required a lot of modifications for small change.

public class User {
    private String name;
    private String email;

    public String getName() {
      return name;
    }

    public void setName(String name) {
      this.name = name;
    }

    public String getEmail() {
      return email;
    }

    public void setEmail(String email) {
      this.email = email;
    }

    public void showUserDetail() {
      System.out.println("Name=" + name + ", Email=" + email);
    }

    public boolean validateEmail(String email) {
      return Pattern.match("Email Pattern", email);
    }

    public void sendEmail(String subject, String content) {
      // ...
    }
  }

High Cohesion

By keeping high cohesion within our code, we end up trying DRY code and reduce duplication of knowledge in our modules. We can easily design, write, and test our code since the code for a module is all located together and works together.

If a class has for example one private field and three methods; when all three methods use this field to perform an operation then the class is very cohesive.

class FooBar {
  private SomeObject foo = new SomeObject();

  public void doSomething1() {
    foo.firstCall();
  }

  public void doSomething2() {
    foo.secondCall();
  }

  public void doSomething3() {
    foo.thirdCall();
  }
}

Example when a class is designed to perform any specific task then that class is called as “High Cohesive class”. This kind of approach is good programming design approach. It can easily maintain and less modifiable.

public class User {
    private String name;
    private String email;

    public String getName() {
      return name;
    }

    public void setName(String name) {
      this.name = name;
    }

    public String getEmail() {
      return email;
    }

    public void setEmail(String email) {
      this.email = email;
    }

    public void showUserDetail() {
      System.out.println("Name=" + name + ", Email=" + email);
    }
  }
  
  public class EmailValidator {
  
    public boolean validateEmail(String email) {
      return Pattern.match("Email Pattern", email);
    }
  }
  
  public class EmailSender {
    public void sendEmail(String subject, String content) {
      // ...
    }
  }

Cohesion vs Coupling

  • Cohesion can represent relationships within a component. Coupling can represent links between components.

  • Cohesion can represent the power of linking functions. Coupling can represent a system that depends on many components.

  • Cohesion evaluates the quality with which a component/module focuses on a unit of work. Coupling is being evaluated to what extent a component/module is associated with other modules.

Maintainable Code

Writing maintainable code helps increase productivity for developers. Having highly maintainable code makes it easier to design new features and write code. Modular, component-based, and layered code increases productivity and reduces risk when making changes.

By keeping code loosely coupled, we can write code within one module without impacting other modules. And by keeping code cohesive, we make it easier to write DRY code that is easy to work with.

While you are writing and working with your code base, ask yourself:

  1. How many modules am I touching to fix this or create this functionality?

  2. How many different places does this change need to take place?

  3. How hard is it to test my code?

  4. Can we improve this by making code more loosely coupled? Can this be improved by making our code more cohesive?

Last updated