Search code examples
c#inversion-of-controlautofacfactorydependency-inversion

Using factory for everything?


My software shall control a device or multiple of them. The program flow is like that it will search for devices on all available interfaces and instantiate an object of the type IDevice for each device found. To state it here, all these objects must be set up during runtime.

Using Autofac to gain dependency inversion (DI) everything has to be set up at the beginning. If something shall be used during runtime "factories" are the thing to use. So lets dig in into an example:

The Program start would look like this:

class Program
{
    static void Main(string[] args)
    {
        var container = ContainerConfig.Configure();

        using (var scope = container.BeginLifetimeScope())
        {
            var app = scope.Resolve<IApplication>();
            app.Run();
        }
    }
}

As soon as "Application" has been resolved everything is set up that is known at start time. The Application then gets started (app.Run()), searching devices and then setting up a list of all devices found. Setting up a device now requires a bunch of "Factories", basically (and here's the problem) "one for each class" that is instantiated through "Device".

class Application : IApplication
{
    readonly Device.Factory _deviceFactory;
    readonly Log.Factory _logFactory;
    readonly DateTime.Factory _dateTimeFactory;
    readonly Incident.Factory _incidentFactory;

    public Application(Device.Factory deviceFactory, Log.Factory logFactory, DateTime.Factory dateTimeFactory, Incident.Factory incidentFactory)
    {
        _deviceFactory = deviceFactory;
        _logFactory = logFactory;
        _dateTimeFactory = dateTimeFactory;
        _incidentFactory = incidentFactory;
    }

    public void Run()
    {
        List<string> listOfDeviceIds = SearchAllDevices();
        List<IDevice> listOfDevices = new List<IDevice>;
        
        foreach(string deviceId in listOfDeviceIds)
        {
            listOfDevices.Add(deviceFactory(_logFactory, _dateTimeFactory, incidentFactory);
        }
    }
}

A "Device" contains a logger tracking something, in this example it will log a time and an incident. So as soon as a new log is set up, it will require a few factories injected.

public class Device : IDevice
{
    public delegate Device Factory(Log.Factory logFactory, DateTime.Factory dateTimeFactory, Incident.Factory incidentFactory);
    
    readonly DateTime.Factory _dateTimeFactory;
    readonly Incident.Factory _incidentFactory;
    readonly Log.Factory _logFactory;
    
    List<Log> ListOfLogs = new List<Log>();
    
    public Device(Log.Factory logFactory, DateTime.Factory dateTimeFactory, Incident.Factory incidentFactory)
    {
        _dateTimeFactory = dateTimeFactory;
        _incidentFactory = incidentFactory;
        _logFactory = logFactory;
    }
    
    public AddLog()
    {
        ListOfLogs().Add(_logFactory(_dateTimeFactory(), _incidentFactory() ));
    }
}


public class Log()
{
    public delegate Log Factory();
    
    public IDateTime DateTime() { get; }
    public IIncident Incident() { get; }
    
    public Log(IDateTime dateTime, IIncident incident)
    {
        DateTime = dateTime;
        Indicent = incident;
    }
}


public DateTime : IDateTime
{
    public delegate DateTime Factory();
    
    //...
}

public Indicent : IIndicent
{
    public delegate Indicent Factory();
    
    //...
}

Now, this is only a little insight of the device class which in real integrates a lot more stuff. And that's were it's now getting messy again.

I have about 30 subclasses or even more that are set up when instantiating a "Device". So because they are set up during runtime they will all need a "Factory" that has to be provided through the "Device" its constructor. Well I guess you see the point.

How do you deal with that? Or am I on the wrong track again? Do I misunderstand something by trying to inverse dependency on everything?

The other point is, that the entire story is about to make it testable, where interface and autofac are the key words. But when using a factory I always have to use the class it self and can't use its interface as reference, at which point DI isn't implemented anymore. ... ?

public class MyClass : IMyClass
{
    public delegate MyClass Factory();
    // ...
}

using it will require MyClass.Factory instead of IMyClass.Factory:

class Main()
{
    MyClass.Factory _myClassFactory;
    Main(MyClass.Factory myClassFactory)
    {
        _myClassFactory = myClassFactory;
    }
    
    foo ()
    {
        IMyClass myInstance = myClassFactory();
    }
}

Solution

  • Why didn't any one tell me! ... ;-) (ok, the links provided in the comments above eventually lead me to this)

    Summary

    Use Func<> instead of delegates and .Factorys. This was my big misunderstanding.

    Refactored code

    The program start remains the same:

    class Program
    {
        static void Main(string[] args)
        {
            var container = ContainerConfig.Configure();
    
            using (var scope = container.BeginLifetimeScope())
            {
                var app = scope.Resolve<IApplication>();
                app.Run();
            }
        }
    }
    

    but then: remove all delegates, remove all .Factorys and only add the dependency (as Func<>) in the constructor that are used in that very class (of course, how could I). Then one will gets this:

    class Application : IApplication
    {
        readonly Func<IIDevice> _deviceFactory;
    
        public Application(Func<IDevice> deviceFactory)
        {
            _deviceFactory = deviceFactory;
        }
    
        public void Run()
        {
            List<string> listOfDeviceIds = SearchAllDevices();
            List<IDevice> listOfDevices = new List<IDevice>;
            
            foreach(string deviceId in listOfDeviceIds)
            {
                listOfDevices.Add(deviceFactory();
            }
        }
    }
    

    and the stuff that is instantiated during runtime:

    public class Device : IDevice
    {       
        readonly Func<ILog> _logFactory;
        
        List<Log> ListOfLogs = new List<Log>();
        
        public Device(Log.Factory logFactory)
        {
            _logFactory = logFactory;
        }
        
        public AddLog()
        {
            ListOfLogs().Add(_logFactory());
        }
    }
    
    
    public class Log()
    {       
        public IDateTime DateTime() { get; }
        public IIncident Incident() { get; }
        
        public Log(Func<IDateTime> dateTimeFactory, Func<IIncident> )
        {
            DateTime = dateTimeFactory();
            Indicent = incidentFactory();
        }
    }
    
    
    public DateTime : IDateTime
    {
        //...
    }
    
    public Indicent : IIndicent
    {
        //...
    }