Sitecore for Developers
Dependency Injection - A Foundation
Dependency Injection Pattern (DI) in itself allows a class's dependencies to be 'given' to the class instead of the class having to create new instances of the dependencies within itself. However, it is prudent to step back and understand some fundamentals that allow one to implement DI and reap the benefits that DI has to offer in terms of more maintainable and accurate code. The next section describes in brief some of these principles - a look at the references listed at the end of the blog is recommended for a deeper understanding.
Design Fundamentals
High CohesionCohesion is a measurement of how responsibilities a class performs are related to each other. The relationships between the responsibilities of a class can be strong - which means they are all geared towards achieving the same goal or are all performing operations that fall within the same functional area. They can also be weak - which means that a class has responsibilities which are not necessarily in the same functional area.
A good real world example would be a car. All responsibilities related to steering being in one class is an example of strong relationships and high cohesion. So is all responsibilities of acceleration being in one class. On the other hand if one were to design a class which would handle some responsibilities of steering and some of acceleration as well, then those responsibilities would not be cohesive - think of the car turning and accelerating at the same time due to some complicated flaw in the logic due to mixed up responsibilities - it would be better to create 2 different classes with more cohesive responsibilities and have them interact with each other for the smooth functioning of the car.
In general high cohesion between the responsibilities of a class is desired.
Low CouplingCoupling is a measurement of how much 2 different classes are dependent on each other. In the car example above, even if acceleration and steering would be separate, but if acceleration needs input from steering every time it performs an action then acceleration is coupled to steering. Coupling in itself is not bad and is necessary at times. But if one needs to change the code or implementation of acceleration every time the implementation of steering has changed then this would be an example of high coupling between acceleration and steering.
In general low coupling between 2 classes is desired.
Separation of Concerns
One way to achieve high cohesion and low coupling is the Separation of Concerns principle, i.e., identifying the unique and related responsibilities from the entire set of responsibilities that are at hand, grouping them into classes of their own and then deciding how these classes would interact with the outside world.
Interface Driven Development
The last point mentioned in 'Separations of Concerns' is related to determining how a related group of functions would interact with the outside world. This is achieved by writing a contract which clearly outlines the responsibilities and their input and output parameters. This contract is what the outside world knows about the class and what all modules wishing to interact with the related group of functions must abide by. The actual implementation of the methods themselves is abstracted away from the outside world and therefore can change at any time without impacting anyone else - as long as the contract is not changed. This contract is called the Interface.
One of the beautiful things that this offers is the ability to switch real implementations of an interface depending on the situation or just simply the ability to use mock implementations instead of real ones for unit testing. We will discuss this in detail in another blog.
Single ResponsibilityIn the ideal world this would mean that one class performs one and only one responsibility. In the real world this is seldom the case. Yet the attempt should always be to break down larger classes containing multiple responsibilities into smaller 'units' which are cohesive. This allows for a palette of classes that can be combined together to achieve larger results, yet which can be tested individually without having to test the entire functionality. Consider the example of acceleration and steering, that we talked about earlier, if the functions were mixed and there was problem with only acceleration, the mechanic would charge you for fixing acceleration and steering both instead of just acceleration!
Inversion of ControlWhen a function orchestrates several objects to achieve a certain result it is dependent on those objects. Any change in the implementation of those objects may result in the breakdown of the orchestrating function - even though by mistake. In other situations a change in the implementation of the objects might cause a change in the orchestrating function. In other words the orchestrating function is controlled to an extent by the lower level objects that it needs. If the orchestrating function would instead, depend on contracts that the lower level objects implemented, then the orchestrating function would be able to switch to any lower object that abides by that contract thus retaining control.
This is Inversion of Control - allowing both the higher and lower level classes to be decoupled from each other as long as they abide by the contract or interface agreement between them.
Sample Implementation of DI
Synopsis
We will be walking through a simple implementation to illustrate DI and also the principles mentioned above. This revolves around a small application built to determine the cost of servicing a vehicle. The cost is set, first, by the age of the vehicle and also depends on whether oil change is required. If an oil change is required the cost also depends on what the cost of the oil change is.
Basic Responsibilities
The primary responsibility of the application is to calculate the service cost - in a sense this is the high level class that is dependent on other lower level classes. The other responsibilities are around determining if an oil change is required and then determining the cost of the oil change.
Interfaces and Implementations
The responsibilities mentioned above are grouped into the following contracts or interfaces:
[sourcecode language="csharp"]
public interface ICheckIfOilChangeIsRequired{bool CheckIfOilChangeIsRequired(DateTime lastServiceDate);}
public interface ICalculateOilChangeCost{double CalculateOilChangeCost(DateTime lastServiceDate);}
public interface ICalculateServiceCost{double CalculateServiceCost(int year, DateTime lastServiceDate);}
[/sourcecode]
Sample implementations that abide by these contracts are as follows:
[sourcecode language="csharp"]
public class OilChangeDeterminator : ICheckIfOilChangeIsRequired{public bool CheckIfOilChangeIsRequired(DateTime lastServiceDate){var oilChangeRequired = false;
// Validationsif (lastServiceDate > DateTime.Now){return oilChangeRequired;}
// If the Last Service Date was more than 90 days ago then an Oil Change is requiredif ((DateTime.Now - lastServiceDate).TotalDays >= 90){oilChangeRequired = true;}
return oilChangeRequired;}}
[/sourcecode]
Note: This class has only one responsibility (that of determining if an oil change is required) and depends on no other class.
[sourcecode language="csharp"]
public class OilChangeCostCalculator : ICalculateOilChangeCost{public double CalculateOilChangeCost(DateTime lastServiceDate){var oilChangeCost = 0;
// Validationsif (lastServiceDate > DateTime.Now){// -1 denotes an error: Last Service Date cannot be in the futurereturn -1;}
// If the Last Service Date was less than 90 days ago then an Oil Change is $0// If the Last Service Date was more than or equal to 90 days ago but less than 180 days ago then an Oil Change is $25// If the Last Service Date was more than or equal to 180 days ago then an Oil Change is $50if ((DateTime.Now - lastServiceDate).TotalDays >= 90 &&(DateTime.Now - lastServiceDate).TotalDays < 180){oilChangeCost = 25;}
if ((DateTime.Now - lastServiceDate).TotalDays >= 180){oilChangeCost = 50;}
return oilChangeCost;}}
[/sourcecode]
Note: This class has only one responsibility (that of determining the cost of an oil change) and depends on no other class.
[sourcecode language="csharp"]
public class ServiceCostCalculator : ICalculateServiceCost{#region Interfaces the class is dependent onprivate readonly ICheckIfOilChangeIsRequired _checkIfOilChangeIsRequired;private readonly ICalculateOilChangeCost _calculateOilChangeCost;#endregion
public ServiceCostCalculator(ICheckIfOilChangeIsRequired checkIfOilChangeIsRequired,ICalculateOilChangeCost calculateOilChangeCost){_checkIfOilChangeIsRequired = checkIfOilChangeIsRequired;_calculateOilChangeCost = calculateOilChangeCost;}
public double CalculateServiceCost(int year, DateTime lastServiceDate){// Validationsif (lastServiceDate > DateTime.Now){// -1 denotes an error: Last Service Date cannot be in the futurereturn -1;}
// The Standard Cost of Service is $35// If the Vehicle is more than 5 years old add another $35// Check if an Oil change is required - if yes add the amount returned by the Oil Change Cost calculatordouble vehicleServiceCharge = 35;
// Get the year from the current datevar currentYear = DateTime.Now.Year;
if (currentYear - year > 5){vehicleServiceCharge += 35;}
if (_checkIfOilChangeIsRequired.CheckIfOilChangeIsRequired(lastServiceDate)){var oilChangeCost = _calculateOilChangeCost.CalculateOilChangeCost(lastServiceDate);if (oilChangeCost != -1){vehicleServiceCharge += oilChangeCost;}else{return oilChangeCost;}}
return vehicleServiceCharge;}}
[/sourcecode]
Note: This class has only one responsibility (that of determining the cost of service) and depends on 2 other abstractions or interfaces - one to determine if an oil change is required and the other to determine the cost of the oil change if an oil change is indeed required.
DI Setup
Setting up DI is about creating a catalog or container of interfaces that an application is exposed to and the concrete implementations that abide by those interfaces. For e.g., in our sample application the class 'OilChangeDeterminator' is a concrete implementation of the interface 'ICheckIfOilChangeIsRequired'. When such a catalog is available to the application the higher level functions in the application can align to interfaces and have a DI framework supply the concrete implementations of those interfaces on demand.
One of the ways of declaring the higher level class's dependencies is through the higher level class's constructor - as we see in the class 'ServiceCostCalculator' and its constructor. The accompanying DI framework simply refers to the catalog of interfaces and their concrete implementations and passes the registered concrete implementation to the higher level class's instance when the higher level class is 'newed' up, i.e., when the higher level class's constructor is called.
There are many frameworks that help implement DI - some of the popular ones being Microsoft's Unity Framework and Castle. We will be using Castle in our example.
The first step in setting up DI using Castle is implementing the interface 'IWindsorContainer' like below:
[sourcecode language="csharp"]
public class DependencyResolutions : IWindsorInstaller{public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store){container.Register(Component.For<ICalculateServiceCost>().ImplementedBy<ServiceCostCalculator>());container.Register(Component.For<ICheckIfOilChangeIsRequired>().ImplementedBy<OilChangeDeterminator>());container.Register(Component.For<ICalculateOilChangeCost>().ImplementedBy<OilChangeCostCalculator>());}}
[/sourcecode]
Then in the application's start up code one needs to instantiate a container of type 'IWindsorInstaller' which is 'WindsorContainer'. On that container object one can call the Install method like below:
[sourcecode language="csharp"]
public static IWindsorContainer Container = new WindsorContainer();Container.Install(FromAssembly.This());
[/sourcecode]
'FromAssembly.This()' locates the first implementation of 'IWindsorInstaller' in the current assembly and loads the 'Container' object with the concrete implementations of the interfaces that the application is exposed to.
DI in action
Once the 'Container' object is set up - Castle will automatically supply concrete implementations when requested by objects in their constructors or one can get the concrete implementation of an interface by resolving to it manually as shown below:
[sourcecode language="csharp"]
public partial class frmServiceDeterminator : Form{public static IWindsorContainer Container = new WindsorContainer();
#region Interfaces the class is dependent onprivate ICalculateServiceCost _calculateServiceCost;#endregion
public frmServiceDeterminator(){InitializeComponent();
// Initialize Dependency InjectionContainer.Install(FromAssembly.This());}
private void btnCalcServiceCost_Click(object sender, EventArgs e){var vehicleYear = Convert.ToInt32(lstbYear.SelectedItem); // Un-boxing :(var lastServiceDate = dtPickServiceDate.Value;
_calculateServiceCost = Container.Resolve<ICalculateServiceCost>();
var serviceCost = _calculateServiceCost.CalculateServiceCost(vehicleYear, lastServiceDate);if (serviceCost != -1){lblServiceCost.Text = string.Concat("The cost of servicing is: $", serviceCost.ToString());}else{lblServiceCost.Text = "The cost of servicing cannot be calculated. Last Service Date is in the future!";}}
private void btnClose_Click(object sender, EventArgs e){this.Close();}}
[/sourcecode]
Here, the form 'frmServiceDeterminator' is the start up form of the application and is dependent on the interface 'ICalculateServiceCost'. This is then resolved to get the concrete implementation of 'ICalculateServiceCost' into the object '_calculateServiceCost'. The concrete implementation is an instance of the class 'ServiceCostCalculator' as determined by the DI container 'DependencyResolutions' as shown in the section 'DI Setup'.
When creating an instance of the class 'ServiceCostCalculator', the DI framework realizes that 'ServiceCostCalculator' in turn depends on the interfaces 'ICheckIfOilChangeIsRequired' and 'ICalculateOilChangeCost' and resolves these dependencies to provide the instance of 'ServiceCostCalculator' with the concrete implementations of 'ICheckIfOilChangeIsRequired' and 'ICalculateOilChangeCost' which are 'OilChangeDeterminator' and 'OilChangeCostCalculator' respectively.
'OilChangeDeterminator' and 'OilChangeCostCalculator' are not dependent on any other interfaces and hence the cycle of DI stops there.
References and further reading
- CLR via C# - Fourth Edition by Jeffrey Richter
- Microsoft .Net: Architecting Applications for the Enterprise - Second Edition by Dino Esposito and Andrea Saltarello