One of the five SOLID principles is Dependency Inversion. To implement according to this principle you can use interfaces and dependency injection. As a software application normally consists of hundreds or thousands of objects you will have to set up a complex tree of object instances and their dependencies. This sounds difficult but of course you can use factory methods and abstract factories and this task will become easy. Furthermore you can use dependency injection framework like Unity. These frameworks will manage the creation of object instances and their dependencies.
Within this article I want to show you a classical way to implement dependency injection with an injection constructor. And in the next step we will use Unity which will help to make the implementation a little bit cleaner.
The example application contains three layers of code. We have a management layer which uses an engine layer which uses a controller layer. The application shall calculate a price offer for a product someone wants to order. On controller level we calculate base costs for manufacturing and shipping. On engine layer we calculate the total costs. And last but not least on management layer we pay attention to product or customer specific conditions like discounts.
The complete object instance tree is created by using interfaces and dependency injection.
Let’s start with the controller layer. We create two interfaces. One for the calculation of the manufacturing costs and another one for the shipping costs.
interface IManufacturingController { int CalculateManufacturingCosts(int units); } interface IShippingController { int CalculateShippingCosts(int units); }
And we implement according objects.
class ManufacturingController : IManufacturingController { public int CalculateManufacturingCosts(int units) { return 5 * units; } } class ShippingController : IShippingController { public int CalculateShippingCosts(int units) { return 10 * units; } }
At next we define an engine which shall be responsible to calculate the total price.
interface IPriceEngine { int GetTotalPrice(int units); }
And we implement this engine. Within this implementation you see the concept of dependency injection by using an injection constructor.
class PriceEngine : IPriceEngine { IManufacturingController _manufacturingController; IShippingController _shippingController; public PriceEngine( IManufacturingController manufacturingController, IShippingController shippingController) { _manufacturingController = manufacturingController; _shippingController = shippingController; } public int GetTotalPrice(int units) { int totalPrice = 0; totalPrice += _manufacturingController.CalculateManufacturingCosts(units); totalPrice += _shippingController.CalculateShippingCosts(units); return totalPrice; } }
The customer manager which adds an additional discount is implemented by using the same principle. The engine is injected as constructor parameter.
interface ICustomerManager { int GetPriceOffer(int units); } class CustomerManager : ICustomerManager { IPriceEngine _priceEngine; public CustomerManager(IPriceEngine priceEngine) { _priceEngine = priceEngine; } public int GetPriceOffer(int units) { int offer; offer = _priceEngine.GetTotalPrice(units); if(units >= 100) { offer = (offer * 9) / 10; } return offer; } }
Now we can use our manager object to calculate a price offer for the customer. The following source code shows a console application which created the needed object instances and uses the manager to calculate the offer.
static void Main(string[] args) { IManufacturingController manufacturingController = new ManufacturingController(); IShippingController shippingController = new ShippingController(); IPriceEngine priceEngine = new PriceEngine(manufacturingController, shippingController); ICustomerManager customerManager = new CustomerManager(priceEngine); Console.WriteLine("offer for 200 units: " + customerManager.GetPriceOffer(200)); Console.ReadKey(); }
As you can see it is not that difficult to create the dependency tree. But in a real application the tree will get very huge and it will contain redundant branches as you reuse components. You can negotiate this complexity and eliminate redundant creation code by using factory methods and abstract factories. But independent whether you use factories or not you have the possibility to use a dependency injection framework. One of the popular frameworks is Unity. It will allow you to create object instances and their dependencies. For example it supports the injection constructor design we have used in our example.
To use Unity you have to install the according NuGet package. At next you have to mark the injection constructors with an according attribute. The following source code shows the adaptation if the engine and manager classes.
class PriceEngine : IPriceEngine { IManufacturingController _manufacturingController; IShippingController _shippingController; [InjectionConstructor] public PriceEngine( IManufacturingController manufacturingController, IShippingController shippingController) { _manufacturingController = manufacturingController; _shippingController = shippingController; } public int GetTotalPrice(int units) { ... } } class CustomerManager : ICustomerManager { IPriceEngine _priceEngine; [InjectionConstructor] public CustomerManager(IPriceEngine priceEngine) { _priceEngine = priceEngine; } public int GetPriceOffer(int units) { ... } }
Now we can use a unity container within the console application. We have to register all interfaces and the according classes. After this registration we can use the Unity container to create an object instance.
static void Main(string[] args) { IUnityContainer unitycontainer = new UnityContainer(); unitycontainer.RegisterType<ICustomerManager, CustomerManager>(); unitycontainer.RegisterType<IPriceEngine, PriceEngine>(); unitycontainer.RegisterType<IManufacturingController, ManufacturingController>(); unitycontainer.RegisterType<IShippingController, ShippingController>(); ICustomerManager customerManager = unitycontainer.Resolve<CustomerManager>(); Console.WriteLine("offer for 200 units: " + customerManager.GetPriceOffer(200)); Console.ReadKey(); }
As you can see we don’t have to know and to set the dependencies now. We have set all dependencies implicit by defining the injection constructor. Unity will use this information to create and inject all needed object instances.
Of course, Unity offers some more features and possibilities. This little example application shall give you a short introduction into the topic of Dependency Injection and will help you to decide whether you want to use Unity within your project.