Any software application consists of numerous classes and the different classes provides different functionalities.So any object oriented application is composed of many classes which interact with each other to achieve the required functionality.
If we look at the architecture of any application then we can easily find that there are high level classes and the low level classes.
For example if are developing a software for a Departmental store then we may define a Store class which may have different attributes and operations.The Store class has many products and can have a large collection of electronic devices.So Store class may refer the ElectronicDevice class.We can say here that the ElectronicDevice is a low level class.
class Department { public string DepartmentName { get; set; } public string Location { get; set; } public List<string> Products { get; set; } public List<ElectronicDevice> electronicDevices; public List<ElectronicDevice> ElectronicDevices { get { return electronicDevices; } set { ElectronicDevicesRepository repsoitory = new ElectronicDevicesRepository(); electronicDevices = repsoitory.GetAll(); } } } class ElectronicDevice { public string Name { get; set; } public string Price { get; set; } }
Though the above design is fine since Store class depends on the ElectronicDevice class so it may need to refer it but still it has one problem.The problem is that the Store class depends directly on the ElectronicDevice class.So if our Store stops selling all the electronic products but just sells the HandHeldDevice then we may need to change the implementation of the Store class. HandHeldDevice class is defined as:
class HandHeldDevice { public string Name { get; set; } public string Price { get; set; } public string DeviceType { get; set; } }
Since HandHeldDevice is a separate class so we will have to change the Store class which causes the following problems:
- If the logic of the Store class is complex enough then it may require a significant change in the Store class.
- We may need to test the existing functionality of the Store class to ensure that it works as expected.
This is where the Dependency Inversion Principle provides the solution .Dependency Inversion Principle which is one of the SOLID design principles is defined as:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
In our example Store class is the High level module whereas ElectronicDevice and the HandHeldDevice classes are the low level modules.Abstraction can be anything which is not an implementation ,for example an abstraction can be an abstract class or an interface.
So if we apply the Dependency Inversion Principle to the above example then we may redesign the solution as:
class Store { public string DepartmentName { get; set; } public string Location { get; set; } public string Products { get; set; } private List<IDevice> devices; public List<IDevice> Devices { get { return devices ; } set { HandHeldDevicesRepository repsoitory = new HandHeldDevicesRepository(); devices = repsoitory.GetAll(); } } } interface IDevice { public string Name { get; set; } public string Price { get; set; } public string DeviceType { get; set; } }
So now instead of referring any particular class in the Department class we are refering the interface IDevice.The IDevice interface can refer to any class such ElectronicDevice or the HandHeldDevice.
Though we are not referring the ElectronicDevice or the HandHeldDevice directly in the Store class we are creating the object of HandHeldDevicesRepository which returns HandHeldDevice class objects.
HandHeldDevicesRepository repsoitory = new HandHeldDevicesRepository();
So the above line still creates a dependency on the HandHeldDevicesRepository repository.So we can not still say that the Store class is not referring any class.
The solution to this problem is using the Dependency Injection.By using the Dependency Injection we pass a references of a class using the constructor or property instead of creating the dependency in the class.So if use constructor injection then we can pass the reference in the constructor of the Sore class.
class Store { public string DepartmentName { get; set; } public string Location { get; set; } public string Products { get; set; } //Declare IDevice variable private IDevice device; public Store(IDevice device) { this.device = device; } private List<IDevice> devices; public List<IDevice> Devices { get { return devices; } set { //device will inform the repository about the type of device object to create DevicesRepository<device> repsoitory = new DevicesRepository<device>(); devices= repsoitory.GetAll(); } } } interface IDevice { public string Name { get; set; } public string Price { get; set; } public string DeviceType { get; set; } }
Now we are passing the device object to the constructor of the Store class using the interface IDevice
public Store(IDevice device) { this.device = device; }
and passing the device object as a type parameter to the generic DevicesRepository class.
DevicesRepository<device> repsoitory = new DevicesRepository<device>();
Now the Store class is totally decoupled from the Device class and will not need to be changed if the Sore starts selling a new device type.We have followed the Dependency Inversion Principle in the above example and introduced interface IDevice interface which the Store class is referring instead of referring a concrete class.
As we have seen Dependency Inversion Principle helps create decoupled architecture in which changes in the application can be easily accommodated without much impact.