Loose Coupling in Java
One of the most key aspects of a Java project is loose coupling. The loose coupling in Java shows how to achieve loose coupling in Java projects or programs. The more loosely coupled structures present in the project or program, the better it is. In loose coupling, a method or class is almost independent, and they have less depended on each other. In other words, the more knowledge one class or method has about another class or method, the more tightly coupled structure is developed. If the classes or methods know less about each other, the more loosely coupled structure comes into existence.
Abstraction is the Key
To achieve loose coupling, one should use abstract classes or interface while performing inheritance. The following examples will make the concept clearer.
Inside the foo method of derived class.
Explanation: The code is simple to comprehend. There are two classes in the program. One is the base class, and another is the derived class. The derived class is being instantiated in the main method, and its foo method is invoked.
However, there is a problem in the above code. The inheritance has led to the tight coupling of classes A and B. Class B knows a lot of stuff about class A. Also, there are fair chances that changes in class A might impact class B. Let's modify the above code to understand it.
Suppose, there is a requirement to add a parameterized constructor containing two integer arguments in the base class. To incorporate the requirement, we have added a constructor in the base class.
/CouplingExample1.java:17: error: constructor A in class A cannot be applied to given types; class B extends A ^ required: int,int found: no arguments reason: actual and formal argument lists differ in length 1 error
Explanation: The output indicates that we have got an error in child class B. However, we did not touch anything in class B; still, we got the error. It is because of the inheritance, that leads to the tightly coupled structure. Ideally, such things should be avoided in a program or project.
Loose Coupling Code
Let's rewrite the above code using an interface.
In the foo method of class B.
Explanation: Now, the parameterized constructor of class A is not affecting class B. It is because class A and class B both are dependent on the abstraction, which is the interface of this case. Note that not only the parameterized constructor, if any other changes are made in class A, then it will also not affect class B, and vice-versa is also true.
In the above code, class A and class B are loosely coupled as they are not directly dependent on each other. Also, whatever changes we make in class A are not visible to class B.
Benefits of loose coupling
Let's understand it with the help of an example.
The computer is using the Dell keyboard.
Explanation: In the above code, the keyboardUsed() method is tightly coupled with the Dell keyboard. It means any other type of keyboard is not entertained by the class Computer. If we use the Lenovo keyboard, we get a compilation error.
/CouplingExample3.java:50: error: incompatible types: LenovoKeyboard cannot be converted to DellKeyboard obj.keyboardUsed(lk);
Practically speaking, a computer should work perfectly with Dell or Lenovo keyboard. However, this is not the case in our example. To make the computer work with the Lenovo keyboard, we have to add another method in the class Computer.
The computer is using the Lenovo keyboard.
Explanation: Now, the computer works for the Lenovo keyboard. However, the keyboardUsed() method is tightly coupled with Lenovo as well as the Dell keyboard. Thus, any other variety of keyboard is not considered by the keyboardUsed() method. Hence, we ended up with the same problem. Also, it is not good to add a method for a specific keyboard in the class Computer.
Think what will happen if we have 50 varieties of keyboards? Handling 50 methods is certainly a tedious task. Also, one has to write a lot of code to add 50 methods. Also, for testing purposes, we have to test all of the added 50 methods, that will be time-consuming.
To avoid such problems, we have to make the method keyboardUsed() loosely coupled with the keyboard. To achieve the same, our keyboardUsed() method should depend upon the interface (remember! Abstraction is the key). Observe the modification done in the following code.
The computer is using the Lenovo keyboard. The computer is using the Dell keyboard.
Explanation: We see that one method is handling keyboards manufactured by the company Dell or Lenovo. It is because the only one method keyboardUsed() of the class Computer is not tightly attached with the class DellKeyboard or LenovoKeyboard. The keyboardUsed() method is dependent on the abstraction (interface Keyboard). Therefore, the method is able to handle any kind of keyboard.
Thus, even if we add 50 more types of keyboard, the keyboardUsed() method is able to handle it. Hence, we have to write a lesser amount of code as compared to the previous code.
Testing the above code is also easier as we have only one method in the class Computer. Previously, we have to test all the methods of class Computer for every type of keyboard.
Also, if in the future the Dell company stops the manufacturing of the keyboard, then we can remove the DellKeboard class. Nothing has to be changed in the class Computer. However, in the previous example, we have to remove the method keyboardUsed(DellKeyboard dk) from the class Computer.
We can observe that if we add something or remove something, we have to make more changes in the previous code and lesser changes to the above code. Thus, it is evident that why one should strive for loosely coupled structures.
Loose Coupling in Day-to-Day Life
There are many instances in our day-to-day life where the loose coupling is used. A few of those instances are mentioned below.