Decoupling in programming means reducing the interdependence between the different components or modules of a software system. It is all about creating components that are independent and can be used elsewhere with minimal changes, and that can be tested and maintained independently.
By reducing interdependence, decoupling allows developers to create software systems that are more resilient, scalable, and easier to maintain.
In practice this means that components can be updated, replaced, or removed without impacting other parts of the system, making the overall system easier to maintain.
Techniques and design patterns to achieve decoupling in Java at code level
Dependency Injection (DI): Dependency injection is a technique where the dependencies of a class are injected through an interface or a constructor. This way, a class is unaware of the concrete implementation of its dependencies.
Observer pattern: The Observer pattern is a design pattern where a subject object notifies its observers of any changes it undergoes.
Adapter pattern: The Adapter pattern is a design pattern that enables the conversion of the interface of a class into another interface that is suitable for the client.
Facade pattern: The Facade design pattern is a pattern that offers a single, simplified public interface that can be used to access a complex system of classes by hiding its complexity.
Service locator pattern: The Service Locator pattern is a pattern that provides a centralized registry called a service locator which allows clients to retrieve services they need by requesting them through the service locator.
Command pattern: The Command pattern is a design pattern where an object is used to represent and encapsulate all the information needed to invoke a method at a later time.
Factory pattern: The Factory pattern is a pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.
In this post, we are going to see examples of decoupling using dependency Injection and then facade pattern.
Example in Java using Dependency Injection (DI) to achieve decoupling
I am demonstrating decoupling using the technique of dependency injection. We are going to make a class independent of its dependencies by decoupling the use of an object from its creation.
Suppose we have a car that has a dependency on a engine. Instead of creating an instance of the engine class inside the car class, we inject the Engine class as a dependency using a constructor,
public interface Engine {
void start();
void stop();
}
public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.start();
}
public void stop() {
engine.stop();
}
}
This makes the Car class independent of any specific implementation of the Engine.
We can create different implementations of the Engine interface, such as GasolineEngine or ElectricEngine
public class Main {
public static void main(String[] args) {
Engine engine = new GasolineEngine(); // create an instance of GasolineEngine
Car car = new Car(engine); // inject the GasolineEngine into the Car
car.start(); // start the car
car.stop(); // stop the car
}
}
This demonstrates how decoupling can help us build interchangeable components that can be used without affecting the rest of the code.
Example in Java using Facade Pattern to achieve decoupling
Let's see how the facade pattern can be used to make code decoupled.
We have an application that uses 3rd party libraries, and they are all over the place, so the application is coupled with these libraries. For example we have
public int f(int x) {
Y y = new Y(x);
return y.someCalculations();
}
where Y is a class from 3rd party.
Solution
We use the facade pattern to wrap the third party library class into your own interface/class that I want exposed to the application.
So when the library changes, we just have to rewrite our MyFacadeClass
public class MyFacadeClass {
private Y y = new Y();
public int someCalculations(int myParam){
y.someCalculations(myParam);
}
}
Note: Facade and Adapter are very similar design patterns, key difference is that Facade doesn't change the domain model of the other subsystem, while Adapter does. This is why I am using Facade here.
This demonstrates how decoupling can help us use third party libraries without coupling them to our own code.
Bigger picture
If the UI and database are tightly coupled, a change in one will likely require changes in the other. However, by decoupling the UI and the database, each can be modified independently of the other, so we can make it possible to change the UI without affecting the database or swap databases without changing the UI.
What if the request for change includes changes on UI and DB ?
When a change is required, such as adding a new field, the API layer has to be updated to handle it, also the UI layer has to display it. Do not forget the database layer in which we need to save data.
In this scenario, decoupling can still be beneficial even if it does not completely eliminate the need for changes in every, because changes can be made independently in their respective layers and this is making the system more flexible.
In the future we can change a position or appearance of added field in the UI without impacting other parts of the system (database or API).
We see a complete decoupling may not always be possible or practical, it still provide benefits in terms of making the system more flexible and maintainable. In addition, decoupling can also help with testing, as each component can be tested independently of the others.
To conclude
Decoupling makes software systems more flexible and easier to maintain by reducing dependencies between different components.
Top comments (0)