Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Service locator and dependency injection

I think it's universally agreed that following is bad

public class Foo
{
    private IService _service;
    public Foo()
    {
        _service = IocContainer.Resolve<IService>();
    }
}

and that following is preferred (Dependency Injection)

public class Foo
{
    private IService _service;
    public Foo(IService service)
    {
    }
}

However now it's up to the consumer to provide the service. The consumer could of course require the IService in the constructor as well, but it seems to be annoying when the hierarchy becomes deeper. At some point someone needs to request the IService from the IoC container - but when...?

A former colleague at my workplace had written a UnitOfWork class for the UoW/Repository pattern like this (using Microsoft ServiceLocator):

public static UnitOfWork
{
    public static IUnitOfWork Current
    {
        get { return ServiceLocator.Current.GetInstance<IUnitOfWork>(); }
    }

    public static void Commit()
    {
        Current.Commit();
    }

    public static void Dispose()
    {
        Current.Dispose();
    }

    public static IRepository<T> GetRepository<T>() where T : class
    {
        return ServiceLocator.Current.GetInstance<IRepository>();
    }
}

and hooked up the IoC using Ninject so a request for IRepository would find the current UoW or create a new if required (if current is disposed). The usage becomes

public class MyController
{    
    public void RunTasks()
    {
        var rep = UnitOfWork.GetRepository<Tasks>();
        var newTasks = from t in rep.GetAll()
                       where t.IsCompleted == false
                       select t;

        foreach (var task in newTasks)
        {
            // Do something
        }

        UnitOfWork.Commit();
    }
}

It does however still suffer from static IoC (service locator) class, but would there be a smarter solution? In this case there is no need to know about the internal dependencies (the static class has no logic), and for testing purposes an alternate IoC configuration can set up everything with mocks - and it's easy to use.

EDIT:

I will try and clarify my confusion with a different example. Suppose I have a standard winforms application with a MainWindow class. When the user click a button, I need to load some data from a database, and pass it to a class which will process the data:

public class MainWindow : Form
{
    public MainWindow()
    {
    }

    private void OnUserCalculateClick(object sender, EventArgs args)
    {
        // Get UoW to connect to DB
        // Get instance of processor
    }
}

How would I get the instance of the processor and the unit of work? Can it be injected into a forms class?

I guess my question boils down to: If I am in a class which has been constructed without Ioc it could be a winform, a ria service class etc. - is it ok to refer to service locator/IoC controller to resolve instances of dependencies, or is there a preferred way of handling these cases? Or am I just doing something wrong...?

like image 744
sondergard Avatar asked Nov 11 '22 08:11

sondergard


1 Answers

About the first part of the question:

The consumer could of course require the IService in the constructor as well, but it seems to be annoying when the hierarchy becomes deeper.

No, the consumer does not require an IService, it requires an IFoo. It does not know that the IFoo it will get has a dependency on IService, only your DI configuration knows that. So, don't worry, you will not end up with this dependency hierarchy hell you describe.

At some point someone needs to request the IService from the IoC container - but when...?

That will happen only in your composition root. So if it's an MVC application, you have somehow configured the MVC framework to use your DI configuration when it needs to instantiate controllers, so internally the framework decides (from routing) that it needs a MyController, and it does something like resolver.Get<MyController>(). So service location is used only up there, not in your controllers or anywhere else.

About the MyController part of the question:

Can't really get the connection with the previous part, but still you can use constructor injection. No static classes (that are not injected, so cannot be swapped or mocked out for testing purposes), no service location.

[As a side note, you could even avoid the extra code about unit of work (probably the ORM you use has one and you already using it at the implementation of your IRepositories). Maybe your repositories can have a SaveChanges method, which will call the unitOfWork's SaveChanges - but that's a matter of preference, and rather irrelevant with the previous discussion].

like image 177
zafeiris.m Avatar answered Nov 15 '22 10:11

zafeiris.m