Search code examples
c#dependency-injectionarchitecturedependency-inversion

architecture with Dependency Inversion (prior to a DI Framework)


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
   }
}

Solution

  • 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.