Coupling, Loosely Coupling and Tightly Coupling

Coupling refers to dependencies that exists between two things. Consider two classes for an example. If one class is highly dependent on the implementation details of another class this is considered as tight coupling. Sometimes even both classes might be dependent on each others implementation details making it more coupled. If a class is tightly coupled with another class, changing implementation details of main (depending) class will affect all other dependent classes. Consider below example of a badly implemented tightly coupled Java code:

 public class Motherboard {
String manufacture = "Intel";
String model = "D101";
boolean onBardVGA = true;
boolean onBoardSound = true;
int listPriority;
.......
}

public class Computer {
Motherboard mb = new Motherboard();

String userName;
String computerName;
int listPriority;
}

As you may see, Motherboard class and Computer class are bound together. Computer class has an object to Motherboard class and motherboard of all the computers will be “Intel D101″ by default. Because of that, created Computer objects are always bound with a particular instance of a Motherboard. If it is required to access details of the computer, we’ll have to use a code similar to what is mentioned below:

 ......
Computer computer = new Computer();
String username = computer.userName;
String mbManufacture = computer.mb.manufacture;
String mbModel = computer.mb.model;
.... 

“listPriority” could be a field that shouldn’t be changed by users. But with this sort of implementation, users of above classes can simplely code “computer.listPriority=10″ to change related values. Also, a simple change of code might end up breaking entire business logic of the application. As an example if variable name “manufacture” was changed to something else, application won’t be able to access manufacture name using “computer.mb.manufacture”. These are some problems associated with tightly coupled code.

In next sections of this document, above tightly coupled Java code will be regularly converted into a loosely coupled code.

Constructors

Object oriented concepts enforce developing reusable code. In above code, Motherboard class is not developed in such reusable manner. Motherboard class cannot be used easily to represent all types of motherboards. We can implement this same code in a reusable manner while decreasing the coupling of the classes by using constructors to dynamically instantiate variables of motherboard class according to the requirement. In such case, when “Computer” class is creating an instance of Motherboard it can mention what sort of a motherboard is required.

 public class Motherboard {
String manufacture;
String model;
boolean onBardVGA;
boolean onBoardSound;
int listPriority;

public Motherboard(String manufacture, String model, boolean onBardVGA, boolean onBoardSound) {
if((manufacture==null && manufacture.equals("")) || (model!=null && !model.equals(""))){
 throw new IllegalArgumentException("All values are mandatory");
}
this.manufacture = manufacture;
this.model = model;
this.onBardVGA = onBardVGA;
this.onBoardSound = onBoardSound;
}
}

public class Computer {
Motherboard mb = new Motherboard("Intel","D101",true,true);

String userName;
String computerName;
int listPriority;
} 

Encapsulation – (Access specifiers, accessors and mutators)

As we discussed before variables like “listPriority” should not be modified outside the class. Also providing users with direct access to variable will increase the coupling of the code. Because of that, variables should not be available directly to other objects. They should be carefully protected so that only required and authorized details will be available or changeable. Instead of creating a “listPriority” variable with default access, it is possible to keep it private and only allow reading that variable as shown below:

 private int listPriority;
public int getListPriority() {
return listPriority;
} 

If above code was used in the Motherboard class, Application will not be able to access “listPriority” directly because it is private to the Motherboard class. Also application will not be able to change value of “listPriority” because only accessor is present but not the mutator.

Accessors and mutators should be used with all variables that should be exposed to other classes to maintain loose coupling. Consider the “manufacture” variable of “Motherboard” class. All other objects are allowed to read and write to this variable and there are no any restrictions. But, if accessors and mutators were not used, applications will have to directly access variable to read and write values. If due to maintenance issue “manufacture” variable was to be renamed, all dependent applications will not function as expected. Because of such issues it is always wise to provide accessors and mutators as a public interface, that other classes should pass through in order to access internal data structures. Accessors and mutators are not only useful in renaming of variables but also in changes of data types, changes of logic and even change of entire backend libraries. They can be used in validating input arguments and formation input/output values. However as that is out of scope of this discussion we won’t be discussing such things in detail. But it is important to remember that if accessors or mutators are used related variables should be made private. Otherwise there won’t be any use of using such methods.

Dependency Injection

After above step, instantiation of “Motherboard” class is well coded and “Motherboard” class is reusable. But as you may see, Computer class can only have one sort of motherboard which is again “Intel D101″. Computer is not loosely coupled with the Motherboard class yet, because a computer is always bound to a single type of motherboard. To avoid this, Design Pattern can be used. But before moving into a complex design patterns we’ll consider simple dependency injection, which is the easiest way (but not the best way) to solve this sort of a problem in loosely coupled manner.

As discussed before out Computer object was dependent with the Motherboard instance. But we do not need a computer to depend on a single motherboard as we expect the Computer class to be able to represent any computer. In-order to do that it is necessary to inject the motherboard into computer, but not to set a fixed motherboard to all computers. This is done by using a technique called dependency injection.

New class called “ComputerBuilder” is used to build the motherboard object and inject it to the computer.

 public class ComputerBuilder {
 private static ComputerBuilder ourInstance;

 public static ComputerBuilder getInstance() {
if(ourInstance == null)
{
ourInstance  = new ComputerBuilder();
}
 return ourInstance;
 }

 private ComputerBuilder () { }

 public Computer createComputer (String username, String manufacture, String model, boolean onBoardVGA, boolean onBoardSound) {
 Motherboard motherboard = new Motherboard(manufacture, model, onBoardVGA, onBoardSound);
 Computer computer = new Computer(username);
 computer.setMotherboard(motherboard);
 return computer;
 }
 public Computer createComputer (String username, Motherboard motherboard) {
 Computer computer = new Computer(username);
 computer.setMotherboard(motherboard);
 return computer;
 }

} 
Now, Motherboard class and Computer class are not dependent. It is possible to inject any type of motherboard into the Computer by passing parameters to “ComputerBuilder” class using any of two createComputer(-) methods.

Builder Pattern – (Abstraction and Inheritance)

If there are various predetermined types of computers (as it is in a production lines), application will have to pass String and Boolean data to “createComputer(-)” methods whenever making a Computer object. Because of that in enterprise wide applications, design patterns like Builder Design Pattern can be used to implement a better solution.

As below code suggests we can create an “AbstractComputerBuilder” which is used to build computers. This abstract builder class is then extended by concrete implementations having details related to each different type of computers. Some examples are “HP_Dv5_Builder” and “HP_Dv7_Builder”. User of this package who is trying to use it to build computer instances will not have to know details about the internal structure of the Computer class, or Motherboard class.

 public abstract class AbstractComputerBuilder {
 protected Computer computer;
 public Computer getComputer() {
 return computer;
 }

public abstract void createComputer(String username);
}

public class HP_Dv5_Builder extends AbstractComputerBuilder {

public void createComputer(String username) {

 Motherboard motherboard = new Motherboard("HP", "DV5", true, true);
computer = new Computer(username);
computer.setMotherboard(motherboard);
}
}

public class HP_Dv7_Builder extends AbstractComputerBuilder {
public void createComputer(String username) {
Motherboard motherboard = new Motherboard("HP", "DV7", true, true);
computer = new Computer(username);
computer.setMotherboard(motherboard);
}
}

public class ComputerAssembler {
private AbstractComputerBuilder builder;

public void setBuilder(AbstractComputerBuilder builder) {
this.builder = builder;
 }

 public Computer getComputer() {
 return builder.getComputer();
 }

public void assembleComputer(String username) {
 builder.createComputer(username);
 }
} 

Application that is using the builder can simply create any type of computer by using this code. With calls like:

ComputerAssembler asm = new ComputerAssembler();

asm.setBuilder(new HP_Dv5_Builder());
asm.assembleComputer("Ayoma");
Computer dv5 = asm.getComputer();

asm.setBuilder(new HP_Dv7_Builder());
asm.assembleComputer("Ayoma");
Computer dv7 = asm.getComputer();

Abstract Builder Pattern – (Polymorphism, Abstraction and Inheritance)

As we only had a single class called “Computer” to build instances, it was not necessary to use an interface. However if there were clearly different computers such as “PersonalComputer”, “SuperComputer” and “ServerComputer”, it’ll be necessary to create separate classes to represent each of these types. In such case, it is necessary to make sure that all “Computer” objects adhere to a contract because builders use functions such as setMotherboard(-) available in ‘computer’ classes. Because of that it’s possible to use an interface and implement it in all types of computer classes to make sure that all classes will contain such commonly used methods. If this was done “AbstractComputerBuilder” class will look like shown below.

 public abstract class AbstractComputerBuilder {
protected IComputer computer;

public IComputer getComputer() {
return computer;
}
public abstract void createComputer(String username);
} 

It doesn’t create an object of “Computer” class anymore but it create an object of “IComputer” interface. Because of that it can be used to refer any instance of a class than implements the “IComputer” interface. “createComputer(-)” method for a type of “ServerComputer” will look like what is shown below:

 public void createComputer(String username) {
Motherboard motherboard = new Motherboard("Intel", "ServerMB121", false, false);
computer = new ServerComputer(username);
computer.setMotherboard(motherboard);
} 
As you may see above, ‘polymorphism’ is highly used in the scenario.

Object Oriented Approach and Loosely Coupling – Summary

This discussion was started with an tightly coupled Java code that can be only used to represent one type of computer having only one type of motherboard and enhanced these classes to be able to represent any computer having any type of motherboard.

Throughout this process we used Encapsulation, to hide unwanted information of a class from one another, so that it can have a well defined public interface. This public interface can be used to prevent application from broking due to changes to the implementation details of the underplaying class. Access specifiers, accessors and mutators were used in encapsulation.

While implementing Builder Pattern and Dependency Injection, we had to use Abstract Classes to provide abstract implementation of classes, so that actual implementation of the class can be provided according to the desired requirements and according to the type of object we need to inject into our main class. When it comes to decoupling different objects, using number of different builders, Abstract Builder Pattern was used where polymorphism and inheritance plays an important role.

According to discussion, it is clear that proper use of object oriented concepts such as encapsulation, inheritance, polymorphism and abstraction will help software engineers to achieve loosely coupled systems.

public class Motherboard {
 String manufacture = "Intel";
 String model = "D101";
 boolean onBardVGA = true;
 boolean onBoardSound = true;
 int listPriority;
 .......
}

public class Computer {
 Motherboard mb = new Motherboard();

 String userName;
 String computerName;
 int listPriority;
}
Share

2 comments on “Coupling, Loosely Coupling and Tightly Coupling

  1. Devaka Cooray June 9, 2011 12:28 PM

    The quick brown fox…. you know ;)

  2. JavaBambino April 24, 2014 5:16 PM

    This is a great article, I have added a link to this article. I feel proud to read it.

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>