If you decide to use Dependency Injection there are two methods of implementing it; Constructor or Setter injection. Constructor injection is where the dependencies are passed in via the constructor and setter injection is where the dependencies are set via a method or a property.
Below are two examples of the SaleProcess class (with dependencies on IDatabaseGateway and ILogger); the first example uses Constructor injection whilst the second uses Setter injection.
public class SaleProcess : ISaleProcess
{
private readonly IDatabaseGateway databaseGateway;
private readonly ILogger log;
public SaleProcess(IDatabaseGateway databaseGateway, ILogger log)
{
this.databaseGateway = databaseGateway;
this.log = log;
}
public void Persist(Sale sale)
{
log.Verbose(1, "Begin Persist Sale");
databaseGateway.Persist(sale);
log.Verbose(2, "End Persist Sale");
}
}
public class SaleProcess : ISaleProcess
{
public IDatabaseGateway DatabaseGateway { get; set; }
public ILogger Log { get; set; }
public void Persist(Sale sale)
{
Log.Verbose(1, "Begin Persist Sale");
DatabaseGateway.Persist(sale);
Log.Verbose(2, "End Persist Sale");
}
}
My opinion is for non-optional dependencies Constructor injection should be used and for optional dependencies Setter injection; so for our example (SaleProcess class) assume that IDatabaseGateway is a non-optional dependency whilst ILogger is an optional dependency, as shown below.
public class SaleProcess : ISaleProcess
{
private readonly IDatabaseGateway databaseGateway;
public ILogger Log { get; set; }
public SaleProcess(IDatabaseGateway databaseGateway)
{
this.databaseGateway = databaseGateway;
}
public void Persist(Sale sale)
{
Log.Verbose(1, "Begin Persist Sale");
databaseGateway.Persist(sale);
Log.Verbose(2, "End Persist Sale");
}
}
The problem with optional dependencies is that they are optional e.g. they might or might not be set, so to avoid “Object Reference Not Set” exceptions you need to put null checks around uses of the optional dependency as shown below.
public class SaleProcess : ISaleProcess
{
private readonly IDatabaseGateway databaseGateway;
public ILogger Log { get; set; }
public SaleProcess(IDatabaseGateway databaseGateway)
{
this.databaseGateway = databaseGateway;
}
public void Persist(Sale sale)
{
if (Log != null)
{
Log.Verbose(1, "Begin Persist Sale");
}
databaseGateway.Persist(sale);
if (Log != null)
{
Log.Verbose(2, "End Persist Sale");
}
}
}
This is horrible and makes the code much harder to read and if a new developer uses the Log variable there is a good chance that they will forget to do the null check, which will lead to runtime exceptions.
This is where the Null pattern can help; the Null pattern is when you have a class which implements the expected interface but its methods have no implementation. Shown below is the ILogger interface and a Null implementation of this interface.
public interface ILogger
{
void Verbose(int eventId, string message);
void Information(int eventId, string message);
void Warning(int eventId, string message);
void Error(int eventId, string message);
}
public class NullLogger : ILogger
{
public void Verbose(int eventId, string message){}
public void Information(int eventId, string message){}
public void Warning(int eventId, string message){}
public void Error(int eventId, string message){}
}
Next we need to make sure that by default the SaleProcess class uses the NullLogger, so by default if the Log variable is called it will use the NullLogger class unless the optional dependency has been set. As shown below.
public class SaleProcess : ISaleProcess
{
private readonly IDatabaseGateway databaseGateway;
public ILogger Log { get; set; }
public SaleProcess(IDatabaseGateway databaseGateway)
{
this.databaseGateway = databaseGateway;
Log = new NullLogger();
}
public void Persist(Sale sale)
{
Log.Verbose(1, "Begin Persist Sale");
databaseGateway.Persist(sale);
Log.Verbose(2, "End Persist Sale");
}
}
So now we do not require any of the code that performs the null check and the SaleProcess class will work correctly regardless of if the optional dependency is set or not.