Tech Point Fundamentals

Sunday, January 23, 2022

Dependency Inversion Principle of SOLID | DIP

Dependency Inversion Principle of SOLID | DIP

Dependency-Inversion-Principle

SOLID Principles are one of the mandatory questions in each and every interview. This is the 6th part of the SOLID Design Principle. In this part, we will walk through the 5th SOLID Design Principle i.e Dependency Inversion Principle (DIP) in detail with live real examples. 


Please read the previous parts over here by below links:


SOLID Design Principles

Single Responsibility Principle

Open Closed Principle

Liskov Substitution Principle

Interface Segregation Principle


Watch our videos here




Introduction


In the SOLID acronym, the 5th letter "D" represents the "Dependency Inversion Principle"In the object-oriented design, the Dependency Inversion Principle is a specific form of loosely coupled software modules. Inversion is nothing but a reversal of the direction. In software applications, dependencies represent a risk, and handling that risk involves some cost.


The principle of dependency inversion refers to the decoupling of software modulesThe dependency inversion principle states that we need to rely on abstractions rather than concrete implementations.


Creating an object of a class using the new operator results in a class being tightly coupled to another class. When one class knows about the design and implementation of another class, changes to one class raise the risk of breaking the other class. 


Such changes can have rippling effects across the application making the application fragile. To avoid such problems, you should write “good code” that is loosely coupled, and to support this you can turn to the Dependency Inversion Principle.




Traditional or Conventional Layered Pattern


Conventional application architecture follows a top-down design approach where a high-level problem is broken into smaller parts. So in this top-down design approach, the high-level design is described in terms of these smaller parts. As a result, high-level modules that get written directly depend on the smaller (low-level) modules. 


So in this approach, higher-level modules depend directly upon lower-level modules to achieve some tasks. This dependency upon lower-level modules limits the reuse opportunities of the higher-level components. The goal of the dependency inversion pattern is to avoid this highly coupled distribution with the mediation of an abstract layer and to increase the re-usability of higher layers.




High-Level vs. Low-Level Modules


The high-level modules basically describe those operations in any application that has more abstract nature and contains more complex logic. These modules orchestrate low-level modules of the application.

The low-level modules contain more specific individual components focusing on details and smaller parts of the application. These modules are used inside the high-level modules in our app.



What does mean by abstraction here?


Abstraction means something which is non-concrete and both interfaces and abstract class can be used for abstraction. 

So, in programming abstraction means we need to create either an interface or abstract class so that the user or client cannot instantiate it directly here and there throughout the application. 

We normally use an interface because the abstract class can also contain both concrete implementations and virtual methods.



Coupling vs. Dependency Inversion


Coupling is the measure of the degree of interdependence between the modules i.e Coupling between modules or components is their degree of mutual interdependence. In other words, how often do changes in class A force related changes in class B.  


Always lower coupling is better in any application. Decoupling is a very general principle applicable in many fields. Dependency Inversion is a specific form of decoupling where you decouple the higher levels of your system from the lower levels by separating them into libraries and using interfaces. 




Inversion of Control (IoC) vs. Dependency Inversion Principle (DIP) vs. Dependency Injection (DI)


You must have heard of Inversion of Control (IoC), Dependency Inversion Principle (DIP), Dependency Injection (DI), IoC Container buzz words in the interviews. 


At a first glance, they all are looking similar but at the next glance, they are looking confusing as well. So let's understand these buzzwords first to understand the Dependency Inversion Principle in a better way.


Inversion-of-Control


Both Inversion of Control (IoC) and Dependency Inversion Principle (DIP) are the high-level Inversion PrinciplesSo the ultimate goal of both IoC and DIP is the same i.e decouplingIn 2004, Martin Fowler published an article on Dependency Injection (DI) and Inversion of Control (IoC).




A common misunderstanding made by many developers is thinking that the DIP and DI are the same. The Dependency Inversion Principle (DIP) is neither Dependency Injection (DI) nor Inversion of Control (IoC)


Is the DIP the same as DI, or IoC? No,  but they work nicely together. Without DI, software components are tightly coupled to each other. So, they are hard to reuse, replace, mock and test, which results in rigid designs.


Dependency Inversion is about the shape of the object upon which the code depends


Dependency Injection (DI) is about how one object acquires a dependency. When a dependency is provided externally, then the system is using DI. 


The Inversion of Control (IoC) is about who initiates the call. If your code initiates a call, it is not IoC, if the container/system/library calls back into code that you provided it, is it IoC.


The Dependency Inversion Principle (DIP), on the other hand, is about the level of abstraction in the messages sent from your code to the thing it is calling


Using DI or IoC with DIP tends to be more expressive, powerful, and domain-aligned, but they are about different dimensions, or forces, in an overall problem. DI is about wiring, IoC is about direction, and DIP is about shape.




Inversion of Control (IoC) 


In Software EngineeringIoC is a programming technique that allows run-time object coupling binding to occur by a framework where the binding is otherwise not known at compile time using static analysis


The IoC recommends the inversion of different kinds of controls in object-oriented design to achieve loose coupling between application modules or classesunlike traditional control flow.


Inversion of Control is a pattern in which the control of the flow in the application is reversed. With IoC, the flow of the control is transferred to an external framework or container. Inversion of control is about who initiates messages. Does your code call into a framework, or does it plug something into a framework, and then the framework calls back? 


The IoC principle helps in designing loosely coupled classes that make them more testable, maintainable, extensible, and modular. It is used to invert different kinds of controls in object-oriented design to achieve loose coupling.


Here control refers to any additional responsibilities other than its main responsibility.  For example flow of an application, flow of object creation, dependent object creation, and binding. You may think of this as the Delegation Principle of Object-Oriented Programming.  The Delegation Principle states that "Don’t do all stuff by yourself, delegate it to the respective classes.


The term was used by Michael Mattsson in a thesis, taken from there by Stefano Mazzocchi and popularized by him in 1999 in a defunct Apache Software Foundation project, Avalon, then further popularized in 2004 by Robert C. Martin and Martin Fowler.


There are multiple IoC implementation techniques like Dependency Injection, Service Locator Pattern, Strategy Design Pattern, etc.


IoC-vs-DI



Dependency Inversion (DIP) 


The Dependency Inversion Principle (DIP) was introduced by Robert C. Martin. The DIP principle also helps in achieving loose coupling between modules or classes but it inverts the dependency, unlike the core responsibility directions. DIP suggests that high-level modules should not depend on low-level modules, both should depend on abstraction.


In contrast to the DIP that concentrates on high and low-level components being dependent on abstractions, IoC speaks more to the control technique utilized to provide the abstraction.


Since in the DIP, the lower-level class is no longer instantiated by its higher-level object, a programming technique is required to instantiate the lower-level class and IoC provides this technique.


So you can think Dependency Inversion Principle (DIP) as a way to implement Inversion of Control (IoC) techniques.




Dependency Injection (DI)


Dependency Injection (DI) is a design pattern used to implement IoC.  Dependency Injection (DI) is a way that implements the IoC principle to invert the creation of dependent objects. Dependency Injection is about how one object knows about another, dependent object. 


It allows the creation of dependent objects outside of a class and provides those objects to a class through different ways.  By using DI, we move the creation and binding of the dependent objects outside of the class that depends on them. So DI is just a way to achieve IoC.


There are different ways of injecting the dependency (DI) like Constructor Dependency Injection, Interface Dependency InjectionMethod  Parameter Dependency Injection, Setter Property  Dependency Injection.    


The Dependency Injection Pattern involves 3 different types of classes. A Client Class (Dependent Class) which depends on the service class, a Service Class (dependency) that provides service to the client class, and an Injector Class that injects the service class object into the client class.




Inversion of Control Container (IoC Container)


IoC Container is a framework that provides the Dependency Injection features. The IoC container is used to manage automatic dependency injection throughout the application so that we as programmers do not need to put more time and effort into it.


There are various IoC containers available such as Unity, Autofac, Ninject, Castle Windsor, StructureMap, DryIoc, etc. These IoC containers create an object of the specified class and inject all the dependent objects at run time and also dispose them at the appropriate time.  


These IoC containers make the developers' life easy so that they don't have to create and manage objects manually. Each and every IoC container manages the scope and lifetime of objects of all the dependencies as well that it resolves using lifetime managers. For example Transient, Singleton, PerInstance, etc.


We cannot achieve loosely coupled classes by using IoC alone. Along with IoC, we also need to use DIP, DI, and IoC containers.


decoupling-modules




Dependency Inversion Principle (DIP)


The Dependency Inversion Principle is the fifth and last SOLID Design Principle. The principle of Dependency Inversion refers to the decoupling of software modules. There are many ways to express the dependency inversion principle.


This principle inverts (reverse) the conventional dependency relationships as there the dependency relationships established from high-level (policy-setting modules) to the low level. Therefore in the conventional or traditional dependency relationship, rendering high-level modules independent of the low-level module implementation details. 


So in DIP instead of high-level modules depending on low-level modules, both depend on abstractions.




Definition of DIP


The Dependency Inversion Principle states that our classes should depend upon interfaces or abstract classes instead of concrete classes and functions. This way, instead of high-level modules depending on low-level modules, both will depend on abstractions.


According to the Dependency Inversion Principle:


Depend upon abstractions, not concretions i.e Entities must depend on abstractions, not on concretions.


What the Dependency Inversion Principle says is that instead of a high-level module depending on a low-level module, both should depend on an abstraction.


In other words:


"The high-level module must not depend on the low-level module, both should depend on abstractions."


The high-level modules, which provide complex logic, should be easily reusable and unaffected by changes in low-level modules, which provide utility features. 


To achieve that, you need to introduce an abstraction that decouples the high-level and low-level modules from each other. Based on this idea, Robert C. Martin’s Dependency Inversion Principle consists of two parts:


  1. High-level modules should not depend on low-level modules. Both should depend on the same abstractions (interface).
  2. Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.


By dictating that both high-level and low-level objects must depend on the same abstraction, this design principle inverts the way some people may think about object-oriented programming. 

When designing the interaction between a high-level module and a low-level module, the interaction should be an abstract interaction between them. 

With the addition of an abstract layer, both high- and lower-level layers reduce the traditional dependencies from top to bottom. 

Nevertheless, the "inversion" concept does not mean that lower-level layers depend on higher-level layers directly. Both layers should depend on abstractions (interfaces) that expose the behavior needed by higher-level layers.




Explanation


In the below diagram without the Dependency Inversion Principle, Object X in Module X refers to Object Y of Module Y

With the Dependency Inversion Principle, an Interface X is introduced as an abstraction in Module X. Here Object X now refers to the Interface X, and Object Y inherits from Interface X.

Dependency-Inversion-Principle


So what the DIP principle has done here is:

i) Both Object X and Object Y now depend on Interface X i.e, Abstraction.

ii) It inverted the dependency that existed from Object X to Object Y into Object Y being dependent on the abstraction (Interface X).




Dependency Inversion Pattern Generalization Restrictions


The presence of interfaces to accomplish the Dependency Inversion Pattern (DIP) has other design implications in an object-oriented program:


  1. All member variables in a class must be interfaces or abstracts.
  2. All concrete class packages must connect only through an interface or abstract class packages.
  3. No class should derive from a concrete class.
  4. No method should override an implemented method.
  5. All variable instantiation requires the implementation of a creational pattern such as the factory method or the factory pattern, or the use of a dependency-injection framework.




Example




public class EmployeeDataAccess
{
	public Employee EmployeeDetails()
	{
		Employee _employee = new Employee();
		//Logic to read data from the databaase or any other source
		return _employee;
	}
}

public class EmployeeBusinessLogic
{
	EmployeeDataAccess _employeeDataAccess;

	public EmployeeBusinessLogic()
	{
		_employeeDataAccess = new EmployeeDataAccess();
	}

	public Employee GetEmployeeDetails()
	{
		return _employeeDataAccess.EmployeeDetails();
	}
}

public class Employee
{
	public int ID { get; set; }
	public string Name { get; set; }
	public string Department { get; set; }
	public int Salary { get; set; }
}		



The above example does not follow the DIP because the EmployeeBusinessLogic class is tightly coupled to the EmployeeDataAccess class as it is directly creating the instance of this class. 




So let's remove this dependency using the Factory Pattern.


 

public class EmployeeDataAccess
{
	public Employee EmployeeDetails()
	{
		Employee _employee = new Employee();
		// Logic to read data from the databaase or any other source
		return _employee;
	}
}

public class DataAccessFactory
{
	public static EmployeeDataAccess CreateInstance()
	{
		return new EmployeeDataAccess();
	}
} 

public class EmployeeBusinessLogic
{
	EmployeeDataAccess _employeeDataAccess;

	public EmployeeBusinessLogic()
	{
		_employeeDataAccess = DataAccessFactory.CreateInstance();
	}

	public Employee GetEmployeeDetails()
	{
		return _employeeDataAccess.EmployeeDetails();
	}
}

public class Employee
{
	public int ID { get; set; }
	public string Name { get; set; }
	public string Department { get; set; }
	public int Salary { get; set; }
}		


Although we implemented the factory design pattern to achieve IoC, the EmployeeBusinessLogic class still uses the concrete EmployeeDataAccess class (EmployeeDataAccess _employeeDataAccess). 




So still our design is tightly coupled even though we have inverted the dependent object creation logic to a separate DataAccessFactory class.



public interface IEmployeeDataAccess
{
	Employee EmployeeDetails();
}

public class EmployeeDataAccess : IEmployeeDataAccess
{
	public Employee EmployeeDetails()
	{
		Employee _employee = new Employee();
		// Logic to read data from the databaase or any other source
		return _employee;
	}
}

public class DataAccessFactory
{
	public static IEmployeeDataAccess CreateInstance()
	{
		return new EmployeeDataAccess();
	}
}

public class EmployeeBusinessLogic
{
	IEmployeeDataAccess _employeeDataAccess;

	public EmployeeBusinessLogic()
	{                
		_employeeDataAccess = DataAccessFactory.CreateInstance();
	}

	public Employee GetEmployeeDetails()
	{
		return _employeeDataAccess.EmployeeDetails();
	}
}

public class Employee
{
	public int ID { get; set; }
	public string Name { get; set; }
	public string Department { get; set; }
	public int Salary { get; set; }
}		



We have implemented the Dependency Inversion Principle (DIP) in our example where the high-level modules (i.e. EmployeeBusinessLogic class) and low-level modules (EmployeeDataAccess class) now depend on abstraction (IEmployeeDataAccess interface). 


Also, abstraction (IEmployeeDataAccess interface) does not depend on details (EmployeeDataAccess class) but details depend on abstraction. 









External Reference Links


Principles of OOD

GRASP Principles

SOLID Principles

Single Responsibility Principle


No comments:

Post a Comment

Please do not enter any HTML. JavaScript or spam link in the comment box.