// Pending picture
- Never depend on everything concrete(Actual Class), only depend on Abstraction.
- High level module should not depend on low level module. They should depend on Abstraction.
- Able to change an implementation easily without altering the high level code.
- By adhering to DIP, you can create systems that are resilient to change, as modifications to concrete implementations do not affect high-level modules.
The Dependency Inversion Principle suggests that high-level modules should not depend on low-level modules, but both should depend on abstractions. Additionally, abstractions should not depend on details; details should depend on abstractions.
-
High-level modules
should not depend onlow-level modules
; both should depend onabstractions
. -
Abstractions
should not depend ondetails
.Details
should depend onabstractions
.
Building a LEGO tower — the bricks (high and low-level modules) connect through smaller bricks (abstractions).
// Higher level module: Application
class Application {
private FileLogger logger;
public Application(FileLogger logger) {
this.logger = logger;
}
public void process() {
logger.log("Application started");
// Additional logic here
}
}
// Lower level module: FileLogger
class FileLogger {
public void log(String message) {
// Code to write the message to a file
}
}
// New Lower level module: ConsoleLogger
class ConsoleLogger {
public void log(String message) {
// Code to write the message to a file
}
}
public class Example {
public static void main(String[] args) {
FileLogger fileLog = new FileLogger();
ConsoleLogger consoleLog = new ConsoleLogger();
// We can't pass the consoleLog as params in Application(consoleLog) without modifying the Application, other wise program gives an compile time error.
Application app = new Application(fileLog);
app.process();
}
}
// Step 1: Define the Abstraction
// Interface : Ilogger
interface ILogger {
void log(String message);
}
// Step 2: Implement the Abstraction
// LLM 1: ConsoleLogger
class FileLogger implements ILogger {
@Override
public void log(String message) {
System.out.println("FileLogger: " + message);
}
}
// LLM 2: ConsoleLogger
class ConsoleLogger implements ILogger {
@Override
public void log(String message) {
System.out.println("ConsoleLogger: " + message);
}
}
// LLM 3: ExternalServiceLogger
class ExternalServiceLogger implements ILogger {
@Override
public void log(String message) {
System.out.println("ExternalServiceLogger: " + message);
// Code to send the message to an external service
// This could involve HTTP requests, dealing with authentication, etc.
}
}
// Step 3: Higher level app now depends upon Abstraction
// HLM 1: Application
class Application {
private ILogger logger;
public Application(ILogger logger) {
this.logger = logger;
}
public void process() {
logger.log("Application started");
// Additional logic here
}
}
public class Example {
public static void main(String[] args) {
ILogger fileLog = new FileLogger();
ILogger consoleLog = new ConsoleLogger();
ILogger externalServiceLog = new ExternalServiceLogger();
// Now we dont need to change for passing fileLog, consoleLog, and externalServiceLog as paramas into Application
Application app1 = new Application(fileLog);
Application app2 = new Application(consoleLog);
Application app3 = new Application(externalServiceLog);
app1.process(); // FileLogger: Application started
app2.process(); // ConsoleLogger: Application started
app3.process(); // ExternalServiceLogger: Application started
}
}
// Higher level module: GoToWork
class GoToWork {
private Metro metro;
public GoToWork(Metro metro) {
this.metro = metro;
}
public void process() {
metro.travel("Transport started");
// Additional logic here
}
}
// Lower level module: Metro
class Metro {
public void travel(String message) {
// Code to write the message to a file
}
}
// New Lower level module: Rapido
class Rapido {
public void travel(String message) {
// Code to write the message to a file
}
}
public class Example {
public static void main(String[] args) {
Metro metroTransport = new Metro();
Rapido rapidoTransport = new Rapido();
// We can't pass the rapidoTransport as params in GoToWork(rapidoTransport) without modifying the GotoWork, other wise program gives an compile time error.
GoToWork app = new GoToWork(metroTransport);
app.process();
}
}
// Step 1: Define the Abstraction
// Interface : ITransport
interface ITransport {
void travel(String message);
}
// Step 2: Implement the Abstraction
// LLM 1: Metro
class Metro implements ITransport {
@Override
public void travel(String message) {
System.out.println("Metro: " + message);
}
}
// LLM 2: Rapido
class Rapido implements ITransport {
@Override
public void travel(String message) {
System.out.println("Rapido: " + message);
}
}
// LLM 3: Uber
class Uber implements ITransport {
@Override
public void travel(String message) {
System.out.println("Uber: " + message);
}
}
// Step 3: Higher level app now depends upon Abstraction
// HLM 1: GoToWork
class GoToWork {
private ITransport transport;
public GoToWork(ITransport transport) {
this.transport = transport;
}
public void process() {
transport.travel("transport started");
// Additional logic here
}
}
public class Example {
public static void main(String[] args) {
ITransport metroTrav = new Metro();
ITransport rapidoTrav = new Rapido();
ITransport uberTrav = new Uber();
// Now we don't need to change for passing metroTrav, rapidoTrav, and uberTrav as paramas into GoToWork
GoToWork transport1 = new GoToWork(metroTrav);
GoToWork transport2 = new GoToWork(rapidoTrav);
GoToWork transport3 = new GoToWork(uberTrav);
transport1.process(); // Metro: transport started
transport2.process(); // Rapido: transport started
transport3.process(); // Uber: transport started
}
}
package calculatorDIP;
// ICalculator Interface: Abstraction Layer for Both High and Low Level Module
interface ICalculator{
void operate(int a, int b);
}
// Calculator Class: High Level Module
public class Calculator {
private ICalculator caloperator;
public Calculator(ICalculator caloperator){
this.caloperator = caloperator;
}
public void process(int a, int b){
caloperator.operate(a, b);
}
}
package calculatorDIP;
// Addition Class: Low Level Module
public class Addition implements ICalculator {
@Override
public void operate(int a, int b){
System.out.println("SUM: "+ (a+b));
}
}
package calculatorDIP;
// Subtraction Class: Low Level Module
public class Subtraction implements ICalculator {
@Override
public void operate(int a, int b){
System.out.println("SUB: "+ (a-b));
}
}
package calculatorDIP;
// Multiplication Class: Low Level Module
public class Multiplication implements ICalculator {
@Override
public void operate(int a, int b){
System.out.println("MUL: "+ (a*b));
}
}
package calculatorDIP;
public class CalMain {
public static void main(String[] args){
ICalculator addition = new Addition();
ICalculator subtraction = new Subtraction();
ICalculator multiplication = new Multiplication();
Calculator operate1 = new Calculator(addition);
Calculator operate2 = new Calculator(subtraction);
Calculator operate3 = new Calculator(multiplication);
operate1.process(10, 5);
operate2.process(20, 10);
operate3.process(30, 10);
}
}
Try to code the example 4 using the abobe diagram.