An application or software consists of several parts which can be distinguishable from one another because of the task they do within the application. There can be parts of code logic, functions, classes or modules. One of the design principles addresses how loosely or tightly these logical parts or modules and functionalities should be couples. Cohesion and Coupling are often named together in this context. In Object Oriented design principles ,it is said that we should aim for “high cohesion and low coupling”. But what cohesion and coupling means ? and which logics should be highly cohesive and which modules should have less coupling ? How can we detect low cohesion and high coupling in code ? In this blog post I will discuss them in detail with examples.
Contents
What is Cohesion or Coherence?
Cohesion means how closely the related functions and code logics are associated and their degree of togetherness. The parts of the code which are similar and solve the same or similar problems should closely depend on each other. The idea is related parts of code should have a high degree of togetherness. Means we should aim for high cohesion in code. The methods which are related, solve the same problem or similar problems should be in one class. Similarly related classes should be in one package. Related functionalities should be in one code module. Now this is very rational. How can we determine which functions or classes are similar enough to be cohesive ? How to relate classes and methods ? The classes and methods should be related based on what they do and what they are used for. One class or methods should focus on solving one problem or related similar problems. It makes sense that we keep the related code in one place because then the code can be developed to provide the highest degree of efficiency and performance. The code written will be robust and reliable. Any future change in logic has to be done in one place and it makes the module or class reusable.
How can we determine a low cohesion ?
When a method in a class can be used to perform different tasks it means low cohesion because the method should do only one task. In this case we should separate out the tasks the method does into different methods.
Lets see this example
public int calculate (int a, int b, String operation) {
if(operation.equals(“add”)) {
return a + b;
} else if(operation.equals(“sub”)) {
return a - b;
} else if(operation.equals(“mul”)) {
return a * b;
} else if(operation.equals(“div”)) {
return a / b;
} else {
throw new IllegalArgumentException(“operation not supported”);
}
}
In the above example one method is doing four tasks so it's not highly cohesive. We should create separate methods which do each operation separately (e.g. add(...), sub(...), mul(...), div(...)).
When different methods in a class do complete unrelated tasks then they have low cohesion. For example a class that returns employee details and calculates tax on employee salary is not a highly cohesive class. There should be two different classes, one Employee providing employee details like name, age, address, salary, department and other TaxCalculator that calculates tax.
A package also should provide functionality which together provides one solution. For example java has the Math module that consists of all mathematical functions needed for mathematical calculations.
What is Coupling?
Coupling happens when two parts are connected to each other directly or indirectly. It measures how tightly or loosely code modules and classes are dependent on each other, meaning the degree of interdependency.
If modules and classes are coupled such that any change in one will force another to change they are said to be tightly coupled. Wikipedia has a good definition and explanation on coupling. We should maintain low coupling in code.
How can we determine a high coupling ?
Classes and methods only should be coupled when they should/could not exist without the other. It becomes difficult to test each class and methods which are tightly coupled. When we want to test one class or method either we need to also test the associated class and method or we need to initialize them properly. It becomes difficult to isolate the class or method we want to test and also difficult to mock the associated classes and methods.
As an example, two different methods might be using a global variable and other than the variable they are not connected to each other. Change in the value of the global variable due to change needed in one method will also force us to check the impact on another method at least.
Another Example, the coupling between the inner and the outer class is tight if an object of an inner class cannot exist alone and needs to depend on its outer class. In this case the tight coupling is good. Another scenario is when you don't want to expose a class B to the outer world, then it makes sense to hide it inside class A and high coupling makes sense.
On the other hand, Let’s assume class A needs another class B to perform some of its tasks or depends on B for some reason. We can associate the object of B in two ways with objects of class A.
Create an object of B within class A and keep the object of B as member variable in A.
Pass an object of B to A while creating object of A to calling methods of A that needs A.
With the first approach the code will look like below
public class B {
public void run() {
System.out.println(“Hello”);
}
}
public class A {
private B b;
A() {
b = new B();
}
public void run() {
b.run();
}
}
With the second approach the code will look like below
public class B {
public void run() {
System.out.println(“Hello”);
}
}
public class A {
private B b;
A(B b) {
this.b = b;
}
public void run() {
b.run();
}
}
The first approach is not good as it is very tightly coupled with A. We need a code change in class A if we need to create a variant of B (subclass) and pass it to A. With the second approach a code change in A is not required. In the first approach we can not test A without B or with a mock object of B but in the second approach we can test both A and B independently and in isolation.
Another example where tight coupling is problemetic is, when class A directly accesses public member variables of class B and the member variables of class B are changed. In this case we need to change everywhere class A is using the member variable of class B. The code for this scenario is shown below
public class B {
public String sayHello = “Hello”;
}
public class A {
private B b;
A(B b) {
this.b = b;
}
public void run() {
System.out.println(b.sayHello);
}
}
Now in case we need to change the variable name sayHello in class B to sayHi we also need to change it in class A inside the method run().
This is why we should not allow access to member variables outside the class directly, rather provide a method to access the variable. A better approach will be
public class B {
private String sayHello = “Hello”;
public String getMessage() { return sayHello; }
}
public class A {
private B b;
A(B b) {
this.b = b;
}
public void run() {
System.out.println(b.getMessage());
}
}
It is not possible to list all such cases that exhibit low cohesion and high coupling. One must understand the concept and apply it in a day to day job to develop better code. Here, in this post I have described what is cohesion or coherence and coupling and the design principle that says we need to follow “high cohesion and low coupling”. With some example we learned how to avoid low cohesion and high coupling.
Comments