I'm studying D.I. I've some issues with the architecture, maybe I'm missing some points.
Suppose I have this non-DI code (I read a list of "Person" from file)
static void Main()
{
PersonReaderFromFile personReader = new PersonReaderFromFile("path-to-file");
Person p = personReader.GetNext(); //return a Person parsed from file line or NULL at EOF
}
class Person
{
//stuffs here
}
class PersonReaderFromFile
{
public Person GetNext()
{
Person person = new Person();
//some logic
return person;
}
}
First, for implementing D.I. patterns, I need interfaces, so I do
static void Main()
{
iPersonReader personReader = new PersonReaderFromFile("path-to-file");
iPerson p = personReader.GetNext();
}
interface iPerson
{
}
class Person : iPerson
{
}
interface iPersonReader
{
iPerson GetNext();
}
class PersonReaderFromFile : iPersonReader
{
public iPerson GetNext()
{
Person person = new Person();
//some logic
return person;
}
}
And now my issue: PersonReaderFromFile depends on Person implementation. It's ok? Or I need an extra class, like a PersonFactory?
static void Main()
{
iPersonFactory factory = new PersonFactory();
iPersonReader personReader = new PersonReaderFromFile("path-to-file", factory);
iPerson person = personReader.GetNext();
}
interface iPerson
{
}
class Person : iPerson
{
}
interface iPersonReader
{
iPerson GetNext();
}
class PersonReaderFromFile : iPersonReader
{
iPersonFactory _factory;
public PersonReaderFromFile(iPersonFactory factory)
{
_factory = factory;
}
public iPerson GetNext()
{
Person person = _factory.CreateNewPerson();
//some logic
return person;
}
}
interface iPersonFactory
{
iPerson CreateNewPerson();
}
class PersonFactory : iPersonFactory
{
iPerson CreateNewPerson()
{
Person person = new Person();
return person;
}
}
Now PersonFactory depends on Person implementation, but it should be correct. Any advice? Tnx to all.
EDIT: I show a sample PersonReaderFromFile implementation
class PersonReaderFromFile
{
StreamReader _fileReader;
public PersonReaderFromFile(string path)
{
_fileReader = new StreamReader(file);
}
public Person GetNext()
{
string line = _fileReader.ReadLine();
if (line == null)
{
_fileReader.Close();
return null;
}
string name, lastName, email;
ParseInformationFromLine(line, out name, out lastName, out email);
Person p = new Person { Name = name, LastName = lastName, Email = email };
return p;
}
private ParseInformationFromLine(string line, out string name, out string lastName, out string email)
{
//I don't think that matters
}
}
PersonReaderFromFile
does not depend on Person
. That class appears to be just a POCO that represents run time data.
PersonReaderFromFile
is dependent on StreamReader
along with ParseInformationFromLine
function
First abstract out those implementation details into their own concerns.
public interface IReadLines {
string ReadLine();
}
public interface IParsePersonInformationFromLine {
void ParseInformationFromLine(string line, out string name, out string lastName, out string email);
}
The target class would explicitly depend on the abstractions
public class PersonReaderFromFile {
private readonly IReadLines reader;
private readonly IParsePersonInformationFromLine parser;
public PersonReaderFromFile(IReadLines reader, IParsePersonInformationFromLine parser) {
this.reader = reader;
this.parser = parser;
}
public Person GetNext() {
string line = reader.ReadLine();
if (line == null) {
return null;
}
string name, lastName, email;
parser.ParseInformationFromLine(line, out name, out lastName, out email);
Person p = new Person { Name = name, LastName = lastName, Email = email };
return p;
}
}
The individual abstractions would have their own implementations to satisfy the desired functionality that would be used at run time. For example the reader would internally still use a StreamReader
to get the lines. PersonReaderFromFile
does not need to know anything about how the line is retrieved. Just that it wants a line when called.
Main
now can be refactored to use Pure DI for example
static void Main() {
IReadLines reader = new ReadLinesImplementation("path-to-file");
IParsePersonInformationFromLine parser = new ParsePersonInformationFromLine();
PersonReaderFromFile personReader = new PersonReaderFromFile(reader, parser);
Person p = personReader.GetNext(); //return a Person parsed from file line or NULL at EOF
}
There are further refactors that can be applied but this is only meant to be a simplified example of the identifying implementation details that should be abstracted out of code that is tightly coupled to implementation concerns.