I want to adapt Dependency Injection and Inversion of Control into my daily development. Say I have an object type of SomeObject
(implements the interface ISomeObject
). I have a class which consumes this object called Data which implements the IData
interface.
public interface ISomeObject {
int ID;
string Name;
bool IsAwesome;
void DoSomeStuffIfAwesome();
}
public Class SomeObject : ISomeObject {
int ID;
string Name;
bool IsAwesome;
void DoSomeStuffIfAwesome() { /*stuff happens here*/ }
}
public interface IData {
List<ISomeObject> GetSomeObjects();
}
public Class Data : IData {
List<ISomeObject> GetSomeObjects()
{
List<ISomeObject> objects = new List<ISomeObject>; // ??? Maybe and cast later?
//do some SQL stuff and get a SqlDataReader object called reader
while(reader.Read()) {
//ISomeObject someObj = ???
//Read into the someObj.ID, someObj.Name and someObj.IsAwesome fields
objects.add(someObj);
}
return objects;
}
}
The GetSomeObjects()
method produces a list of ISomeObject
objects. But I don't want Data.cs
to have anything related to SomeObject
hardcoded into it. I want some form of Dependency Injection to resolve the issue at runtime. What's the best way to handle this? I've considered the following:
1. Pass instance of SomeObject
into Data
's constructor. This way I can get its type with .GetType()
, store that into a private System.Type
variable in Data.cs
, and use Activator.CreateInstance
in the loop to create new objects to be added to the list. Data
would need to know about the SomeObject
class specifically to cast, if I understand it correctly.
2. Pass an instance of my IoC container to Data
's constructor and just resolve the object type using container.Resolve<ISomeObject>()
. This would make unit testing the GetSomeObjects()
method difficult without utilizing my IoC container. I've read I shouldn't utilize the IoC container during unit testing, and should manually pass in what I need into methods.
3. Pass an ISomeObject
object that has been instantiated as a SomeObject
- I would then use that to create the object via some built in method, such as SomeObject.GenerateList(IDataReader reader)
.
You can delegate the creation of the object out to something else;
public interface ISomeObjectFactory {
ISomeObject Create(IDataReader reader);
}
which has a single responsibility to create instances of ISomeObject
using System.Collections.Generic;
using System.Data;
public interface IDbConnectionFactory {
///<summary>
/// Creates a connection based on the given database name or connection string.
///</summary>
IDbConnection CreateConnection(string nameOrConnectionString);
}
public class Data : IData {
private IDbConnectionFactory dbConnectionFactory;
ISomeObjectFactory someObjectFactory;
private string CONNECTION_STRING = "Connection string here";
public Data(IDbConnectionFactory dbConnectionFactory, ISomeObjectFactory objectFactory) {
this.dbConnectionFactory = dbConnectionFactory;
this.someObjectFactory = objectFactory;
}
public List<ISomeObject> GetSomeObjects() {
var objects = new List<ISomeObject>();
//do some SQL stuff and return a data reader
using (var connnection = dbConnectionFactory.CreateConnection(CONNECTION_STRING)) {
using (var command = connnection.CreateCommand()) {
//configure command to be executed.
command.CommandText = "SELECT * FROM SOMEOBJECT_TABLE";
connnection.Open();
using (var reader = command.ExecuteReader()) {
while (reader.Read()) {
//...Logic to populate item
var someObject = someObjectFactory.Create(reader);
if (someObject != null)
objects.Add(someObject);
}
}
}
}
return objects;
}
}
that way Data
is only dependent on abstractions and not on concretions. Those can be determined/configured in the composition root at runtime.