(You Can Hack It, Architecture and Design) => { Dependency Injection; }

Contents

  1. ABCompany is hiring
    1. Coding exercise interview question
    2. Rookie’s solution
    3. Feedback of interviewer
    4. Food for thought
    5. Second solution
  2. Dig deep to dependency injection
    1. Revealing
    2. What on earth is dependency injection
    3. Third solution
  3. Dependency injection, what you must know
    1. Different kinds of DI
      1. Setter injection
      2. Constructor injection
      3. Dependency locator
    2. DI, C# and reflection
    3. DI and the mutability of polymorphism
      1. The mutability of polymorphism
      2. Suit the remedy to the case
  4. IoC container
    1. Meet IoC container
    2. Different kinds of IoC container
      1. Heavyweight IoC container
      2. Lightweight IoC container
    3. IoC container in .Net world
  5. Code Gallery

1. ABCompany is hiring

1.1. Coding exercise interview question

ABCompany is hiring software architect, and every candidates should do their coding exercise first. It is called sales taxes calculation, and basically you can Google it and will find lots of similar questions.

Basic sales tax is applicable at a rate of 9% on all goods except Food; Book; Medical

Import duty is applicable at a rate of 4% on all goods, with no exemptions

When I purchase items, I receive a receipt which lists the name of all the items and their price(including tax). It also includes the total cost of the items and the total amounts of sales taxes paid. The rounding rules for sales tax are that for a tax rate of n%, a shelf price of p contains (np/100 rounded up to 2 decimal places) amount of sales tax.

1 imported CD 10.99
1 perfume 19.99
1 headache pills 4.65
1 imported chocolates 16.45
Receipt
1 imported CD 12.42
1 perfume 21.79
1 headache pills 4.65
1 imported chocolates 17.11
Sales Taxes 3.89
Total 55.97

1.2. Rookie’s solution

A potential candidate gives his first solution:


namespace YouCanHackIt.ArchitectureDesign.DI.FirstSolution
{
using System;
public class Implementation
{
public void BuyProducts()
{
var importedCD = new Product("imported CD", 10.99m, false, true);
var perfume = new Product("perfume", 19.99m, false, false);
var headachePills = new Product("headache pills", 4.65m, true, false);
var importedChocolates = new Product("imported chocolates", 16.45m, true, true);
ShoppingCart shoppingCart = new ShoppingCart();
shoppingCart.Buy(importedCD);
shoppingCart.Buy(perfume);
shoppingCart.Buy(headachePills);
shoppingCart.Buy(importedChocolates);
shoppingCart.Checkout();
}
}
public class Product
{
public Product(string name, decimal price, bool isTaxFree, bool isImport)
{
this.Name = name;
this.Price = price;
this.IsTaxFree = isTaxFree;
this.IsImport = isImport;
}
public string Name { get; set; }
public decimal Price { get; set; }
public bool IsTaxFree { get; set; }
public bool IsImport { get; set; }
}
public class ShoppingCart
{
public decimal TotalPrices { get; set; }
public decimal TotalTaxes { get; set; }
public void Buy(Product product)
{
decimal taxes = 0m;
if (!product.IsTaxFree)
{
taxes += product.Price * 0.09m;
}
if (product.IsImport)
{
taxes += product.Price * 0.04m;
}
taxes = decimal.Round(taxes, 2, MidpointRounding.AwayFromZero);
this.TotalPrices += product.Price + taxes;
this.TotalTaxes += taxes;
Console.WriteLine(product.Name);
Console.WriteLine(product.Price + taxes);
}
public void Checkout()
{
Console.WriteLine("Total Taxes:");
Console.WriteLine(this.TotalTaxes);
Console.WriteLine("Total Prices:");
Console.WriteLine(this.TotalPrices);
}
}
}

the result is:YouCanHackIt.ArchitectureDesign.DI.FirstSolutionCool! not bad, it works!

1.3. Feedback of interviewer

First things first, from the perspective of architecture and design, there are some problems:

1. Product class violated Open closed principle , for now Food; Book; Medical those kinds of product will NOT be applied a tax on them, for the other kinds of product will be applied, this is a potential changing point and product class should be closed to itself not matter which kind of product changes its tax applicable, and product class should be open if it is still available for extension like if Cloth is decided not to apply tax.

2. ShoppingCart class violated Single responsibility principle , in the first solution, ShoppingCart can buy products, can calculate products taxes, can calculate total price, can calculate total taxes, can…etc, too much responsibilities.

3. ShoppingCart class violated Dependency inversion principle , actually how to calculate the tax is the key point of the whole solution and this is the main topic of this article.

1.4. Food for thought

According to the feedback, what we can do to improve the design?

1. Add a enum in indicate which tax type of the product, and let product class has this enum as a property instead of just a bool to say this product should pay tax or not. Thus, even if tax type changed or new tax type appears, we only need to add a new value for this enum type, and product class will not be changed.

2. Let ShoppingCart class only do one job: shopping, or in code: add product. And maybe we can have a order item class to represent which product you buy and how many of them you buy. And add a order class to contain all the order items and generate total prices and total taxes. Maybe at last we will need a receipt class to represent your receipt, we will see.

3. DI, tricky staff, and I will talk about it right away. But before we go to DI, the first thought may be to use a Strategy Pattern on tax calculation, good idea. Different kinds of good need different tax calculation strategy, right? For example, headachePills will need Medical tax calculation strategy which is zero tax, perfume will need normal tax calculation strategy which is perfume’s price * 9% ,etc.

1.5. Second solution


namespace YouCanHackIt.ArchitectureDesign.DI.SecondSolution
{
using System;
using System.Collections.Generic;
using System.Linq;
public class Implementation
{
public void BuyProducts()
{
var importedCD = new Product("imported CD", 10.99m, SalesTaxType.Default, DutyTaxType.Import);
var perfume = new Product("perfume", 19.99m, SalesTaxType.Default, DutyTaxType.Domestic);
var headachePills = new Product("headache pills", 4.65m, SalesTaxType.Medical, DutyTaxType.Domestic);
var importedChocolates = new Product("imported chocolates", 16.45m, SalesTaxType.Food, DutyTaxType.Import);
ShoppingCart shoppingCart = new ShoppingCart();
shoppingCart.Buy(importedCD);
shoppingCart.Buy(perfume);
shoppingCart.Buy(headachePills);
shoppingCart.Buy(importedChocolates);
Cashier cashier = new Cashier();
cashier.Checkout(shoppingCart);
}
}
public enum SalesTaxType
{
Default,
Food,
Book,
Medical,
}
public enum DutyTaxType
{
Domestic,
Import,
}
public class Product
{
public Product(string name, decimal price, SalesTaxType salesTaxType, DutyTaxType dutyTaxType)
{
this.Name = name;
this.Price = price;
this.SalesTaxType = salesTaxType;
this.DutyTaxType = dutyTaxType;
}
public string Name { get; set; }
public decimal Price { get; set; }
public SalesTaxType SalesTaxType { get; set; }
public DutyTaxType DutyTaxType { get; set; }
}
public class OrderItem
{
public Product Item { get; set; }
public int Quantity { get; set; }
public decimal TotalTaxes { get; set; }
public decimal TotalPrices { get; set; }
}
public class Order
{
public Order()
{
this.Items = new List<OrderItem>();
}
public List<OrderItem> Items { get; set; }
public decimal TotalTaxes { get; set; }
public decimal TotalPrices { get; set; }
}
public class ShoppingCart
{
public ShoppingCart()
{
this.Items = new List<Product>();
}
public List<Product> Items { get; set; }
public void Buy(Product item)
{
this.Items.Add(item);
}
public void Remove(Product item)
{
var itemToRemove = this.Items.LastOrDefault(i => i.Name.Equals(item.Name));
if (itemToRemove != null)
{
this.Items.Remove(itemToRemove);
}
}
}
public class Cashier
{
private ITaxService _salesTaxService = new SalesTaxService();
private ITaxService _dutyTaxService = new DutyTaxService();
public void Checkout(ShoppingCart shoppingCart)
{
var order = this.BuildOrder(shoppingCart.Items);
this.Print(order);
}
public Order BuildOrder(IList<Product> products)
{
var result = new Order();
var productGroup = products.GroupBy(product => product.Name);
foreach (var item in productGroup)
{
var orderItem = new OrderItem { Item = item.First(), Quantity = item.Count() };
this.ProcessPrice(orderItem);
result.Items.Add(orderItem);
}
result.TotalTaxes = result.Items.Sum(item => item.TotalTaxes);
result.TotalPrices = result.Items.Sum(item => item.TotalPrices);
return result;
}
private void ProcessPrice(OrderItem orderItem)
{
decimal netPrices = orderItem.Item.Price * orderItem.Quantity;
decimal salesTax = this._salesTaxService.CalculateTax(orderItem.Item.SalesTaxType.ToString(), netPrices);
decimal dutyTax = this._dutyTaxService.CalculateTax(orderItem.Item.DutyTaxType.ToString(), netPrices);
decimal totalTaxes = decimal.Round(salesTax + dutyTax, 2, MidpointRounding.AwayFromZero);
decimal totalPrice = netPrices + totalTaxes;
orderItem.TotalTaxes = totalTaxes;
orderItem.TotalPrices = totalPrice;
}
private void Print(Order order)
{
Console.WriteLine();
foreach (var item in order.Items)
{
Console.WriteLine("{0} {1} : {2:F2}", item.Quantity, item.Item.Name, item.TotalPrices);
}
Console.WriteLine("Sales Taxes: {0:F2}", order.TotalTaxes);
Console.WriteLine("Total: {0:F2}", order.TotalPrices);
}
}
public interface ICalculator
{
decimal Calculate(decimal value);
}
public class DefaultTaxCalculator : ICalculator
{
public decimal Calculate(decimal value)
{
return value * 0.09m;
}
}
public class FoodTaxCalculator : ICalculator
{
public decimal Calculate(decimal value)
{
return value * 0m;
}
}
public class BookTaxCalculator : ICalculator
{
public decimal Calculate(decimal value)
{
return value * 0m;
}
}
public class MedicalTaxCalculator : ICalculator
{
public decimal Calculate(decimal value)
{
return value * 0m;
}
}
public class DomesticTaxCalculator : ICalculator
{
public decimal Calculate(decimal value)
{
return value * 0m;
}
}
public class ImportTaxCalculator : ICalculator
{
public decimal Calculate(decimal value)
{
return value * 0.04m;
}
}
public interface ITaxService
{
decimal CalculateTax(string taxType, decimal value);
}
public class SalesTaxService : ITaxService
{
public decimal CalculateTax(string taxType, decimal value)
{
ICalculator calculator = null;
switch (taxType)
{
case "Food":
calculator = new FoodTaxCalculator();
break;
case "Book":
calculator = new BookTaxCalculator();
break;
case "Medical":
calculator = new MedicalTaxCalculator();
break;
case "Default":
default:
calculator = new DefaultTaxCalculator();
break;
}
return calculator.Calculate(value);
}
}
public class DutyTaxService : ITaxService
{
public decimal CalculateTax(string taxType, decimal value)
{
ICalculator calculator = null;
switch (taxType)
{
case "Domestic":
calculator = new DomesticTaxCalculator();
break;
case "Import":
calculator = new ImportTaxCalculator();
break;
default:
calculator = new DomesticTaxCalculator();
break;
}
return calculator.Calculate(value);
}
}
}

2. Dig deep to dependency injection

2.1. Revealing

Let’s re-think about the hiring story, what we can learn, why it is related to dependency injection, and under what circumstances people create dependency injection.

In the first solution, we have a basic object-oriented design, we have Product class, we have ShoppingCart class. But as you can see, this solution has bad smell. ShoppingCart is so depended on specific tax calculation algorithm. If something changed in different tax calculation, we need to change ShoppingCart also. In this situation, people come up with an idea: how about let ShoppingCart or whatever client class only need “something” which you give it an input and it will give you an output. As regards what concrete “something”, ShoppingCart or client class doesn’t care. Someone will give ShoppingCart or client class a concrete “something”, the process of creating a concrete “something” and giving it to ShoppingCart or client class, is so called dependency injection, which means I inject dependency to you, you don’t need to care what you depends.

As  object-oriented design evolving, one important principle is to segregate changes and don’t  impact on unchanged part. In order to do so, client class depends on something abstract instead of depending on concrete ones, but client class need concrete thing to get job done. So we can see the paradox, client class need something, but it cannot create that directly inside. To solve this problem, people invent a pattern: client class depends on  abstract or interface which is called injection point, and use a service provide class to provide concrete service, and inject the concrete service to client class injection point. According to different configuration/situation/whatever changes, service provide class can create different service, and we can inject them to client class.

2.2. What on earth is dependency injection

In software engineering, dependency injection is a software design pattern that implements inversion of control for resolving dependencies. A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it. The service is made part of the client’s state.[1] Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.

From wikipedia

 

DI

From the UML diagram we can see, Client need Service, but it doesn’t depends on Service. Client only need to know IService interface/abstraction. Builder will create a concrete Service and inject it to Client.

2.3 Third solution


namespace YouCanHackIt.ArchitectureDesign.DI.ThirdSolution
{
using System;
using System.Collections.Generic;
using System.Linq;
public class Implementation
{
public void BuyProducts()
{
var importedCD = new Product("imported CD", 10.99m, SalesTaxType.Default, DutyTaxType.Import);
var perfume = new Product("perfume", 19.99m, SalesTaxType.Default, DutyTaxType.Domestic);
var headachePills = new Product("headache pills", 4.65m, SalesTaxType.Medical, DutyTaxType.Domestic);
var importedChocolates = new Product("imported chocolates", 16.45m, SalesTaxType.Food, DutyTaxType.Import);
ShoppingCart shoppingCart = new ShoppingCart();
shoppingCart.Buy(importedCD);
shoppingCart.Buy(perfume);
shoppingCart.Buy(headachePills);
shoppingCart.Buy(importedChocolates);
#region Manually Dependency Injection.
// Builds all calculators.
List<ICalculator> calculators = new List<ICalculator>
{
new DefaultTaxCalculator(),
new FoodTaxCalculator(),
new BookTaxCalculator(),
new MedicalTaxCalculator(),
new DomesticTaxCalculator(),
new ImportTaxCalculator()
};
// Builds ICalculatorResolver instance and injects all calculators.
ICalculatorResolver calculatorResolver = new CalculatorResolver(calculators);
// Builds ITaxService instance and injects ICalculatorResolver.
ITaxService taxService = new TaxService(calculatorResolver);
// Builds Cashier instance and injects ITaxService.
Cashier cashier = new Cashier(taxService);
#endregion
cashier.Checkout(shoppingCart);
}
}
public enum SalesTaxType
{
Default,
Food,
Book,
Medical,
}
public enum DutyTaxType
{
Domestic,
Import,
}
public class Product
{
public Product(string name, decimal price, SalesTaxType salesTaxType, DutyTaxType dutyTaxType)
{
this.Name = name;
this.Price = price;
this.SalesTaxType = salesTaxType;
this.DutyTaxType = dutyTaxType;
}
public string Name { get; set; }
public decimal Price { get; set; }
public SalesTaxType SalesTaxType { get; set; }
public DutyTaxType DutyTaxType { get; set; }
}
public class OrderItem
{
public Product Item { get; set; }
public int Quantity { get; set; }
public decimal TotalTaxes { get; set; }
public decimal TotalPrices { get; set; }
}
public class Order
{
public Order()
{
this.Items = new List<OrderItem>();
}
public List<OrderItem> Items { get; set; }
public decimal TotalTaxes { get; set; }
public decimal TotalPrices { get; set; }
}
public class ShoppingCart
{
public ShoppingCart()
{
this.Items = new List<Product>();
}
public List<Product> Items { get; set; }
public void Buy(Product item)
{
this.Items.Add(item);
}
public void Remove(Product item)
{
var itemToRemove = this.Items.LastOrDefault(i => i.Name.Equals(item.Name));
if (itemToRemove != null)
{
this.Items.Remove(itemToRemove);
}
}
}
public class Cashier
{
private readonly ITaxService _taxService;
public Cashier(ITaxService taxService)
{
this._taxService = taxService;
}
public void Checkout(ShoppingCart shoppingCart)
{
var order = this.BuildOrder(shoppingCart.Items);
this.Print(order);
}
public Order BuildOrder(IList<Product> products)
{
var result = new Order();
var productGroup = products.GroupBy(product => product.Name);
foreach (var item in productGroup)
{
var orderItem = new OrderItem { Item = item.First(), Quantity = item.Count() };
this.ProcessPrice(orderItem);
result.Items.Add(orderItem);
}
result.TotalTaxes = result.Items.Sum(item => item.TotalTaxes);
result.TotalPrices = result.Items.Sum(item => item.TotalPrices);
return result;
}
private void ProcessPrice(OrderItem orderItem)
{
decimal netPrices = orderItem.Item.Price * orderItem.Quantity;
decimal salesTax = this._taxService.CalculateTax(orderItem.Item.SalesTaxType.ToString(), netPrices);
decimal dutyTax = this._taxService.CalculateTax(orderItem.Item.DutyTaxType.ToString(), netPrices);
decimal totalTaxes = decimal.Round(salesTax + dutyTax, 2, MidpointRounding.AwayFromZero);
decimal totalPrice = netPrices + totalTaxes;
orderItem.TotalTaxes = totalTaxes;
orderItem.TotalPrices = totalPrice;
}
private void Print(Order order)
{
Console.WriteLine();
foreach (var item in order.Items)
{
Console.WriteLine("{0} {1} : {2:F2}", item.Quantity, item.Item.Name, item.TotalPrices);
}
Console.WriteLine("Sales Taxes: {0:F2}", order.TotalTaxes);
Console.WriteLine("Total: {0:F2}", order.TotalPrices);
}
}
public interface ICalculator
{
string Name { get; }
decimal Calculate(decimal value);
}
public class DefaultTaxCalculator : ICalculator
{
public DefaultTaxCalculator()
{
this.Name = "Default";
}
public string Name { get; private set; }
public decimal Calculate(decimal value)
{
return value * 0.09m;
}
}
public class FoodTaxCalculator : ICalculator
{
public FoodTaxCalculator()
{
this.Name = "Food";
}
public string Name { get; private set; }
public decimal Calculate(decimal value)
{
return value * 0m;
}
}
public class BookTaxCalculator : ICalculator
{
public BookTaxCalculator()
{
this.Name = "Book";
}
public string Name { get; private set; }
public decimal Calculate(decimal value)
{
return value * 0m;
}
}
public class MedicalTaxCalculator : ICalculator
{
public MedicalTaxCalculator()
{
this.Name = "Medical";
}
public string Name { get; private set; }
public decimal Calculate(decimal value)
{
return value * 0m;
}
}
public class DomesticTaxCalculator : ICalculator
{
public DomesticTaxCalculator()
{
this.Name = "Domestic";
}
public string Name { get; private set; }
public decimal Calculate(decimal value)
{
return value * 0m;
}
}
public class ImportTaxCalculator : ICalculator
{
public ImportTaxCalculator()
{
this.Name = "Import";
}
public string Name { get; private set; }
public decimal Calculate(decimal value)
{
return value * 0.04m;
}
}
public interface ICalculatorResolver
{
ICalculator Resolve(string name);
}
public class CalculatorResolver : ICalculatorResolver
{
private readonly IEnumerable<ICalculator> _calculators;
public CalculatorResolver(IEnumerable<ICalculator> calculators)
{
this._calculators = calculators;
}
public ICalculator Resolve(string name)
{
ICalculator calculator = this._calculators.FirstOrDefault(calc => calc.Name == name);
if (calculator == null)
{
throw new ArgumentException("Calculator was not found", name);
}
return calculator;
}
}
public interface ITaxService
{
decimal CalculateTax(string taxType, decimal value);
}
public class TaxService : ITaxService
{
private readonly ICalculatorResolver _calculatorResolver;
public TaxService(ICalculatorResolver calculatorResolver)
{
this._calculatorResolver = calculatorResolver;
}
public decimal CalculateTax(string taxType, decimal value)
{
ICalculator calculator = this._calculatorResolver.Resolve(taxType);
return calculator.Calculate(value);
}
}
}

3. Dependency injection, what you must know

3.1 Different kinds of DI

There are at least three ways we can inject dependency.

3.1.1 Setter injection

Setter injection is the client exposes a setter method that the injector uses to inject the dependency. Especially in C#, we use property setter to do so.

Code sample:


namespace YouCanHackIt.ArchitectureDesign.DI.Samples
{
public class SetterInjection
{
public void Run()
{
ICalculator calculator1 = new DefaultTaxCalculator();
ICalculator calculator2 = new FoodTaxCalculator();
ICalculator calculator3 = new BookTaxCalculator();
TaxClient taxClient = new TaxClient();
taxClient.Calculator = calculator1;
var tax1 = taxClient.GetTax(10m); //tax1=0.9
taxClient.Calculator = calculator2;
var tax2 = taxClient.GetTax(10m); //tax2=0
taxClient.Calculator = calculator3;
var tax3 = taxClient.GetTax(10m); //tax3=0.1
}
public interface ICalculator
{
decimal Calculate(decimal value);
}
public class DefaultTaxCalculator : ICalculator
{
public decimal Calculate(decimal value)
{
return value * 0.09m;
}
}
public class FoodTaxCalculator : ICalculator
{
public decimal Calculate(decimal value)
{
return value * 0m;
}
}
public class BookTaxCalculator : ICalculator
{
public decimal Calculate(decimal value)
{
return value * 0.01m;
}
}
public class TaxClient
{
public ICalculator Calculator { get; set; }
public decimal GetTax(decimal price)
{
return this.Calculator.Calculate(price);
}
}
}
}

3.1.2 Constructor injection

Constructor injection is the dependencies are provided through a class constructor.

Code sample:


namespace YouCanHackIt.ArchitectureDesign.DI.Samples
{
public class ConstructorInjection
{
public void Run()
{
ICalculator calculator1 = new DefaultTaxCalculator();
ICalculator calculator2 = new FoodTaxCalculator();
ICalculator calculator3 = new BookTaxCalculator();
TaxClient taxClient1 = new TaxClient(calculator1);
var tax1 = taxClient1.GetTax(10m); //tax1=0.9
TaxClient taxClient2 = new TaxClient(calculator2);
var tax2 = taxClient2.GetTax(10m); //tax2=0
TaxClient taxClient3 = new TaxClient(calculator3);
var tax3 = taxClient3.GetTax(10m); //tax3=0.1
}
public interface ICalculator
{
decimal Calculate(decimal value);
}
public class DefaultTaxCalculator : ICalculator
{
public decimal Calculate(decimal value)
{
return value * 0.09m;
}
}
public class FoodTaxCalculator : ICalculator
{
public decimal Calculate(decimal value)
{
return value * 0m;
}
}
public class BookTaxCalculator : ICalculator
{
public decimal Calculate(decimal value)
{
return value * 0.01m;
}
}
public class TaxClient
{
private ICalculator _calculator;
public TaxClient(ICalculator calculator)
{
this._calculator = calculator;
}
public decimal GetTax(decimal price)
{
return this._calculator.Calculate(price);
}
}
}
}

3.1.3 Dependency locator

Dependency locator is the system provide a service locator point, when client need service, it can get service from service locator point. This approach changes from passive acceptance to active seeking.

ServiceLocator


namespace YouCanHackIt.ArchitectureDesign.DI.DevLibSolution
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using DevLib.Ioc;
public class Implementation
{
public Implementation()
{
this.Compose();
}
public void BuyProducts()
{
var importedCD = new Product("imported CD", 10.99m, SalesTaxType.Default, DutyTaxType.Import);
var perfume = new Product("perfume", 19.99m, SalesTaxType.Default, DutyTaxType.Domestic);
var headachePills = new Product("headache pills", 4.65m, SalesTaxType.Medical, DutyTaxType.Domestic);
var importedChocolates = new Product("imported chocolates", 16.45m, SalesTaxType.Food, DutyTaxType.Import);
ShoppingCart shoppingCart = new ShoppingCart();
shoppingCart.Buy(importedCD);
shoppingCart.Buy(perfume);
shoppingCart.Buy(headachePills);
shoppingCart.Buy(importedChocolates);
Cashier cashier = ServiceLocator.Current.GetInstance<Cashier>();
cashier.Checkout(shoppingCart);
}
// Injects all services.
private void Compose()
{
IocContainer container = new IocContainer();
ServiceLocator.SetLocatorProvider(() => container);
container.RegisterAssembly<ICalculator>(Assembly.GetExecutingAssembly());
container.Register<ICalculatorResolver>(c => new CalculatorResolver(c.GetAllInstances<ICalculator>()));
container.Register<ITaxService>(c => new TaxService(c.Resolve<ICalculatorResolver>()));
container.Register<Cashier>(c => new Cashier(c.Resolve<ITaxService>()));
}
}
public enum SalesTaxType
{
Default,
Food,
Book,
Medical,
}
public enum DutyTaxType
{
Domestic,
Import,
}
public class Product
{
public Product(string name, decimal price, SalesTaxType salesTaxType, DutyTaxType dutyTaxType)
{
this.Name = name;
this.Price = price;
this.SalesTaxType = salesTaxType;
this.DutyTaxType = dutyTaxType;
}
public string Name { get; set; }
public decimal Price { get; set; }
public SalesTaxType SalesTaxType { get; set; }
public DutyTaxType DutyTaxType { get; set; }
}
public class OrderItem
{
public Product Item { get; set; }
public int Quantity { get; set; }
public decimal TotalTaxes { get; set; }
public decimal TotalPrices { get; set; }
}
public class Order
{
public Order()
{
this.Items = new List<OrderItem>();
}
public List<OrderItem> Items { get; set; }
public decimal TotalTaxes { get; set; }
public decimal TotalPrices { get; set; }
}
public class ShoppingCart
{
public ShoppingCart()
{
this.Items = new List<Product>();
}
public List<Product> Items { get; set; }
public void Buy(Product item)
{
this.Items.Add(item);
}
public void Remove(Product item)
{
var itemToRemove = this.Items.LastOrDefault(i => i.Name.Equals(item.Name));
if (itemToRemove != null)
{
this.Items.Remove(itemToRemove);
}
}
}
public class Cashier
{
private readonly ITaxService _taxService;
public Cashier(ITaxService taxService)
{
this._taxService = taxService;
}
public void Checkout(ShoppingCart shoppingCart)
{
var order = this.BuildOrder(shoppingCart.Items);
this.Print(order);
}
public Order BuildOrder(IList<Product> products)
{
var result = new Order();
var productGroup = products.GroupBy(product => product.Name);
foreach (var item in productGroup)
{
var orderItem = new OrderItem { Item = item.First(), Quantity = item.Count() };
this.ProcessPrice(orderItem);
result.Items.Add(orderItem);
}
result.TotalTaxes = result.Items.Sum(item => item.TotalTaxes);
result.TotalPrices = result.Items.Sum(item => item.TotalPrices);
return result;
}
private void ProcessPrice(OrderItem orderItem)
{
decimal netPrices = orderItem.Item.Price * orderItem.Quantity;
decimal salesTax = this._taxService.CalculateTax(orderItem.Item.SalesTaxType.ToString(), netPrices);
decimal dutyTax = this._taxService.CalculateTax(orderItem.Item.DutyTaxType.ToString(), netPrices);
decimal totalTaxes = decimal.Round(salesTax + dutyTax, 2, MidpointRounding.AwayFromZero);
decimal totalPrice = netPrices + totalTaxes;
orderItem.TotalTaxes = totalTaxes;
orderItem.TotalPrices = totalPrice;
}
private void Print(Order order)
{
Console.WriteLine();
foreach (var item in order.Items)
{
Console.WriteLine("{0} {1} : {2:F2}", item.Quantity, item.Item.Name, item.TotalPrices);
}
Console.WriteLine("Sales Taxes: {0:F2}", order.TotalTaxes);
Console.WriteLine("Total: {0:F2}", order.TotalPrices);
}
}
public interface ICalculator
{
string Name { get; }
decimal Calculate(decimal value);
}
public class DefaultTaxCalculator : ICalculator
{
public DefaultTaxCalculator()
{
this.Name = "Default";
}
public string Name { get; private set; }
public decimal Calculate(decimal value)
{
return value * 0.09m;
}
}
public class FoodTaxCalculator : ICalculator
{
public FoodTaxCalculator()
{
this.Name = "Food";
}
public string Name { get; private set; }
public decimal Calculate(decimal value)
{
return value * 0m;
}
}
public class BookTaxCalculator : ICalculator
{
public BookTaxCalculator()
{
this.Name = "Book";
}
public string Name { get; private set; }
public decimal Calculate(decimal value)
{
return value * 0m;
}
}
public class MedicalTaxCalculator : ICalculator
{
public MedicalTaxCalculator()
{
this.Name = "Medical";
}
public string Name { get; private set; }
public decimal Calculate(decimal value)
{
return value * 0m;
}
}
public class DomesticTaxCalculator : ICalculator
{
public DomesticTaxCalculator()
{
this.Name = "Domestic";
}
public string Name { get; private set; }
public decimal Calculate(decimal value)
{
return value * 0m;
}
}
public class ImportTaxCalculator : ICalculator
{
public ImportTaxCalculator()
{
this.Name = "Import";
}
public string Name { get; private set; }
public decimal Calculate(decimal value)
{
return value * 0.04m;
}
}
public interface ICalculatorResolver
{
ICalculator Resolve(string name);
}
public class CalculatorResolver : ICalculatorResolver
{
private readonly IEnumerable<ICalculator> _calculators;
public CalculatorResolver(IEnumerable<ICalculator> calculators)
{
this._calculators = calculators;
}
public ICalculator Resolve(string name)
{
ICalculator calculator = this._calculators.FirstOrDefault(calc => calc.Name == name);
if (calculator == null)
{
throw new ArgumentException("Calculator was not found", name);
}
return calculator;
}
}
public interface ITaxService
{
decimal CalculateTax(string taxType, decimal value);
}
public class TaxService : ITaxService
{
private readonly ICalculatorResolver _calculatorResolver;
public TaxService(ICalculatorResolver calculatorResolver)
{
this._calculatorResolver = calculatorResolver;
}
public decimal CalculateTax(string taxType, decimal value)
{
ICalculator calculator = this._calculatorResolver.Resolve(taxType);
return calculator.Calculate(value);
}
}
}

3.2 DI, C# and reflection

No matter which kind of DI we use, finally Builder need to create concrete Services, which means Builder need to know all kinds of specific Services and “new” them. Most of modern languages create a object by “new” it, for example in C#, IService service1 = new ServiceA(); Now a new problem has been reared up: Builder need to know all Services at design time,  what kind of dependency resolving is that, it totally depends on dependencies. Yes, but if one language had reflection capability, it would all be different. Reflection enhances dependency injection pattern, it gives Builder ability to create services dynamically, the Builder don’t need to know specific types of services at design time, it can choose and “new” different services at runtime.  DI+Reflection=Super Duper.

Reflection can combine with dependency locator, can combine with setter injection, and also can combine with constructor injection. With reflection, DI now truly follow the open closed principle. Reflection offers the possibility to create a generic DI framework.

3.3 DI and the mutability of polymorphism

3.3.1 The mutability of polymorphism

The mutability of polymorphism is a concept that I create which means in a segregated changing point, or here we say injection point, if once we inject some concrete service and this injection point seldom change or never change again, I call it immutable polymorphism; if at runtime the injection point may change service according to different condition and it happens frequently, I call it mutable polymorphism; if the injection point may change service or may not change, the change happens somethings but not so frequently,  I call it potential polymorphism.

  • immutable polymorphism – once be injected, the injection point will not change anymore
  • potential polymorphism – one instance of client may not change the injection service, but different instance of client may choose different injection service
  • mutable polymorphism – the injection point will change service at runtime

3.3.2 Suit the remedy to the case

According to the mutability of polymorphism,  we can use different kinds of DI.

For those immutable polymorphism, it is better to use Dependency locator + config file, because the only way to change injection point service is to change config file, and once config file is settled, the injection point will not change at runtime. The dependency decision is come from config file.

For those potential polymorphism, it is better to choose Constructor injection, because construction injection also has the chance to choose different service according to context by passing different constructor parameter. But once constructor is called and the client is created, the injection point is settled. So different client instance may have different service, but each one of them can not change its injection point at runtime.

For those mutable polymorphism, it is better to use Setter injection, because setter is the most flexible way to inject dependency, you can change it anytime. The injection point can change anytime at runtime by assigning different service to setter.

4. IoC container

4.1 Meet IoC container

It is inevitable that people invent IoC container, so what is IoC container and how it was born?

DI is so important that people wish to follow it in software engineering, but it is painful to create DI classes every time. As more and more people need DI, someone focus on creating a generic reusable DI component – IoC Container.

IoC Container is a generic framework to create dependencies and inject them automatically when required. It automatically creates objects based on request or config and inject them when required. IoC Container helps us to manage dependencies in a simple and easy way.

4.2 Different kinds of IoC container

There are lots of excellent IoC Containers that are available. And most of them support all kinds of injection(setter, constructor, method, etc…). While depends on the way we use them, we can divide them into two categories: heavyweight IoC container and lightweight IoC container.

4.2.1 Heavyweight IoC container

Usually heavyweight IoC container refers to those who use config file (normally Xml file) as a dependency decision, and take change of all kinds of instance creation through the whole system, the framework and configuration is very complex and it is not easy to harness.

For example, Spring/Spring.NET is a heavyweight IoC container, this kind of framework is very powerful and stable, has lots of features, but it is hard to use, special in agile project.

4.2.2 Lightweight IoC container

Some IoC container is very agile, can use with or without config file, and basically use setter or constructor injection. This kind of IoC container is lightweight IoC container. They are not so powerful but the features they provide are good enough. They give you as much freedom as you can have to control them in code. But they also have cons, not so stable, usually rely on string or parameters in code.  It is good enough for agile projects and those who don’t need fancy DI features.

MEF is a typical lightweight IoC container.

4.3 IoC container in .Net world

Today, we can find lots of IoC container in .Net world. Each of them has its own characteristics.

  • Spring.NET
    • Interception
    • Good documentation
    • Commercial support available
  • Castle Windsor
    • Well documented and used by many
    • Typed factories
    • Commercial support available
  • Autofac
    • Easy API
    • Next-generation IoC Container
    • Commercial support available
  • Unity
    • Interception
    • Good documentation
    • Microsoft support
  • Ninject
    • Easy API
    • Next-generation IoC Container
  • MEF
    • Easy API
    • Next-generation IoC Container
    • Ships with .Net 4.0 and after versions
    • Microsoft support
  • DevLib.Ioc
    • My own code
    • Support from .Net 2.0
    • Super easy and lightweight

5. Code Gallery

You can find all the codes of this blog on GitHub

You can find DevLib.Ioc on CodePlex or NuGet

One thought on “(You Can Hack It, Architecture and Design) => { Dependency Injection; }”

Leave a comment