This post is part of the series SOLID Wash Tunnel.
Definition
"Dependency injection makes a class independent of its dependencies. It achieves that by decoupling the usage of an object from its creation."
- Stackify
Dependency injection is used to implement the Dependency Inversion and Inversion of Control principles. It involves 3 types of players:
- Dependent - A class which depends on some
dependency
. - Dependency - A class which offers some kind of service. This is often served via an
abstraction
to decouple thedependent
from a concrete implementation of thedependency
. - Injector - A class/component/framework which injects a
dependency
, into thedependent
.
Our custom IoC Container is considered to be aninjector
.
Types of DI
As stated above the injector
injects the dependency
into the dependent
class. This can be achived in 3 ways:
- Constructor Injection - The
injector
supplies thedependency
through thedependent
class constructor. - Method Injection - The
injector
supplies thedependency
through any method of thedependent
class, which accepts saiddependency/abstraction
. - Property Injection - The
injector
supplies thedependency
through a public property of thedependent
class.
Our custom IoC Container only supports constructor injection, so we will be using that throughtout the project.
Implementation
Dependency Injection is used across the whole code base!
For example lets elaborate the individual parts the PriceCalculator
class, and relate them to the definitions made above for DI.
- PriceCalculator - Is the
dependent
since it needs theICurrencyRateConverter
to do its work. - ICurrencyRateConverter - Is the
dependency
or more precisely theabstraction
that is injected via constructor injection. It offers rate conversion functionality that thePriceCalculator
depends on. - Our IoC container (Not shown here) - Is the
injector
since it supplies thePriceCalculator
with an instance ofICurrencyRateConverter
.
public interface IPriceCalculator
{
int Discount { get; }
Money Calculate(IWashProgram program, Currency currency);
}
public abstract class PriceCalculator : IPriceCalculator
{
protected readonly ICurrencyRateConverter converter;
public PriceCalculator(ICurrencyRateConverter converter)
{
this.converter = converter;
}
public virtual Money Calculate(IWashProgram program, Currency currency)
{
Money totalPrice = program
.GetWashSteps()
.Select(x => x.Price)
.Aggregate((x, y) => x + y);
return converter.Convert(totalPrice, currency);
}
}
public class IndividualPriceCalculator : PriceCalculator
{
public override int Discount => 0;
public IndividualPriceCalculator(ICurrencyRateConverter converter)
: base(converter) { }
}
public class CompanyPriceCalculator : PriceCalculator
{
public override int Discount => 20;
public CompanyPriceCalculator(ICurrencyRateConverter converter)
: base(converter) { }
public override Money Calculate(IWashProgram program, Currency currency)
{
Money totalPrice = base.Calculate(program, currency);
return totalPrice - (Discount / 100m * totalPrice);
}
}
Principles
Principle | Applied | Explanation |
---|---|---|
SRP | ✅ | Deals only with calculation of prices. |
OCP | ✅ | Different calculations are performed by extending the base PriceCalculator class. |
LSP | ✅ | You can supply an IndividualPriceCalculator or CompanyPriceCalculator where a PriceCalculator is expected. |
ISP | ✅ | PriceCalculator (the client) makes use of all the methods of IPriceCalculator . |
DIP | ✅ | High level modules like PriceCalculator do not depend on the low level modules like CurrencyRateConverter , LegacyCurrencyRateConverter . They depended on the abstraction ICurrencyRateConverter . |
Continue the series on Simple Factory.
If you found this article helpful please give it a share in your favorite forums 😉.
The solution project is available at GitHub.