While dependency injection via constructors is a popular and effective form of dependency injection is has several problems.
- It forces instantiation of dependency objects that may not be needed
- It cannot handle some methods of class A needing class B and some methods of class B needing class A
- It can create bloated constructor argument lists
We can get around problems 1 and 2 by using deferred instantiation. That is, instead of passing actual objects we just pass types. The injectee instantiates the class if and when it is needed. Of course, this approach has its own limitations.
- We cannot enforce the correct base class or interface while editing, but we can at run time
- We cannot easily specify which instance of the dependency to inject
namespace DIViaType
public abstract class BaseLoggingProvider
{
public abstract string GetName();
public class ProductionLoggingProvider : BaseLoggingProvider
public override string GetName()
return "Production logging provider";
public override void Log(string s)
// Write to log file
}
}
public class TestLoggingProvider : BaseLoggingProvider
public override string GetName()
return "Test logging provider";
public override void Log(string s)
Console.WriteLine(s);
}
}
}
namespace DIViaType
public abstract class BaseDataProvider
{
private BaseLoggingProvider _loggingProvider = null;
protected BaseDataProvider(Type loggingProviderType = null)
if (loggingProviderType != null && !loggingProviderType.IsSubclassOf(typeof(BaseLoggingProvider)))
this.loggingProviderType = loggingProviderType ?? typeof(ProductionDataProvider);
public BaseLoggingProvider loggingProvider
get
{
if (_loggingProvider == null)
_loggingProvider = (BaseLoggingProvider)Activator.CreateInstance(loggingProviderType);
}
return _loggingProvider;
}
public abstract string GetName();
public class ProductionDataProvider : BaseDataProvider
public ProductionDataProvider(Type loggingProvider) : base(loggingProvider) { }
public override string GetName()
return "Production data provider";
public override void CloseConnection()
loggingProvider.Log("Closing connection");
}
public override Settings GetSettings()
loggingProvider.Log("Getting settings");
throw new Exception("Connection is closed");
public override bool IsConnectionOpen()
return (sqlConnection != null && sqlConnection.State == ConnectionState.Open);
public override void OpenConnection()
loggingProvider.Log("Opening Connection");
}
}
public class TestDataProvider : BaseDataProvider
private bool isConnectionOpen = false;
public TestDataProvider(Type loggingProvider) : base(loggingProvider) { }
public override string GetName()
return "Test data provider";
public override void CloseConnection()
loggingProvider.Log("Closing connection");
public override Settings GetSettings()
loggingProvider.Log("Getting settings");
throw new Exception("Connection is closed");
public override bool IsConnectionOpen()
return isConnectionOpen;
public override void OpenConnection()
loggingProvider.Log("Opening connection");
}
}
namespace DIViaType
public class Settings
{
public bool isProductionMode;
public class Configuration
{
private Type dataProviderType;
private BaseDataProvider _dataProvider;
public Configuration(Type dataProviderType = null, Type loggingProviderType = null)
if (dataProviderType != null && !dataProviderType.IsSubclassOf(typeof(BaseDataProvider)))
if (loggingProviderType != null && !loggingProviderType.IsSubclassOf(typeof(BaseLoggingProvider)))
this.dataProviderType = dataProviderType ?? typeof(ProductionDataProvider);
public BaseDataProvider dataProvider // In real life this is private or protected
{
get
{
if (_dataProvider == null)
_dataProvider = (BaseDataProvider)Activator.CreateInstance(dataProviderType, loggingProviderType);
}
return _dataProvider;
}
public Settings GetSettings()
Settings settings;
dataProvider.OpenConnection();
settings = dataProvider.GetSettings();
dataProvider.CloseConnection();
return settings;
}
}
namespace DIViaType
class Program
{
static void Main(string[] args)
Configuration configuration = new Configuration();
settings = configuration.GetSettings();
Console.WriteLine(string.Format("Data provider is {0}", configuration.dataProvider.GetName()));
}
}
namespace DIViaTypeTest
public class Tests
{
[Test]
public void ConfigurationTest()
Settings settings;
Configuration configuration = new Configuration(typeof(TestDataProvider), typeof(TestLoggingProvider));
settings = configuration.GetSettings();
Assert.IsFalse(settings.isProductionMode);
Assert.AreEqual("Test data provider", configuration.dataProvider.GetName());
[Test]
public void ConfigurationFailTest()
Settings settings;
Configuration configuration = new Configuration(typeof(TestLoggingProvider), typeof(TestDataProvider));
settings = configuration.GetSettings();
Assert.IsFalse(settings.isProductionMode);
}
}
}
Running the tests, we see they pass and fail as expected. For those of us who are worried that CreateInstance is slower than New, the test says it only takes one millisecond more.
The impact of the architecture on production code is minimal although it does mean we cannot share instances of dependencies unless they implement a factory, static, or singleton pattern. Instantiation in tests is more awkward, but we have achieved deferred instantiation of dependencies without significantly altering the constructor dependency injection pattern.
No comments:
Post a Comment