Saturday, December 10, 2016

DIP - Dependency Inversion Principle

DIP says - The high-level modules/classes should not depend upon low-level modules/classes. Both should depend upon abstractions. Secondly, abstractions should not depend upon details. Details should depend upon abstractions.

It means that if a class has dependencies on other classes, it should rely on dependencies' interface in place of their concrete types. Basically it helps in developing loosely coupled code.

DIP is some way related to Dependency Injection (DI) pattern but it doesn't imply Dependency Injection. DIP just says that higher layers of your application should not directly depend on lower layers. DIP doesn’t say anything about how higher layers know what lower layer to use. This could be done by using Dependency Injection or Service Locator patterns.

Let's understand DIP with an example: Think of an application which has 4 layers, Presentation Layer, Application Layer, Business Layer and Data Access Layer. The Presentation layer is the highest layer and directly depends on or communicates with the Application Layer. The Application layer is higher level than Business Layer and depends on or communicate with the Business Layer and so on. 

When DIP is applied this relationship between layers are reversed. The Presentation Layer defines the abstractions it needs to interact with the Application Layer.  The Application Layer defines the abstractions it needs to interact with the Business Layer. The Business Layer defines the abstractions it needs to interact with the Data Access Layer. Basically the higher layer defines the abstraction and lower layer implements those abstractions.

Presentation Layer ----> Services Layer -----> Business Components Layer ----> Data Access Layer
DIP may put abstraction in the layers defining them, for example Presentation Layer contains Presentation Layer logic and the Service Layer Abstractions (abstract classes and interfaces). The Services Layer contains Service Logic and Business Layer Abstractions (abstract classes and interfaces) and so on. In this case of DIP application the Data Access Layer depends upon the Business Layer, the Business Layer depends on the Service Layer and the Service Layer depends on the Presentation Layer. The dependencies (references) are inverted hence the name of the principle.


I don't follow the above approach in my projects what I do instead is define Presentation, Services, Business and Data Access Layers as different assemblies. I also make Interfaces as different assemblies like Service Interfaces, Business Interfaces, and Data Access Interfaces.   


Since the layers interacts with each other through interfaces, the second intent (abstractions should not depend  on details. Details should depend on abstractions) of the DIP is also full-filled.  The best part about this principle is, it makes the code unit testable, like in the above example each layer can be tested independently.

Lets see DIP with an example, lets assume that we have a high level class called Manager class which represent a person that manages the workers and a low level class called Worker which represents the person a Manger class manages.

public class Worker {
   public void Work()
   {
        // working...
   }
}

public class Manager {
   private IList<Worker> objWorkers;
   public Manager(List<Worker> workers)
  {
          objWorkers =  workers;
  }
   public void Manage()
  {
        foreach (var worker in objWorkers )
             worker.Work();
   }
}

Let's say now few specialised workers are being introduced. We created a new class SuperWorker for this.

public class SuperWorker {
   public void Work()
   {
        // working...
   }
}

Now will have to be modified Manager class to accommodate SuperWorker class, which is going to break OCP. So how should we resolve this issues? This is where the DIP comes into the picture. Now create and IWorker interface and make Manager use IWorker instead of Work class. 

public interface IWorker{
    public void Works();
}

public class Worker : IWorker {
   public void Work()
   {
        // working...
   }
}

public class SuperWorker : IWorker {
   public void Work()
   {
        // working...
   }
}

public class Manager {
   private IList<IWorkable > objWorkers;
   public Manager(List<IWorkable > workers)
  {
          objWorkers =  workers;
  }

   public void Manage()
  {
        foreach (var worker in objWorkers )
             worker.Work();
   }
}

Let's say now a Robot is being introduce the only thing we need to do is make Robot implement the IWorker interface & no additional changes in the existing classes.

public class Robot: IWorker {
   public void Work()
   {
        // working...
   }
}

No comments:

Post a Comment