Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Service Locator easier to use than dependency Injection?

The application I am working on is relying on Autofac as DI container and one of the reasons that made me decide to use it, among others, was the delegate factory feature (see here)

This works fine for all cases where I need to recreate the same elements several times as is the case of some reports and related screens. Some reports (even those of the same type) are executed concurrently but they change only by their user-defined parameters so it makes sense (I think) to inject factories in order to create instances, passing the free parameters and leave the rest to the application.

The problem comes with the fact that each report is made of a variable number of sub reports (tasks) and each task implements an ITask interface. Each report may have up to 50 different tasks to use and each task encapsulates a precise business operation. One option I have is to inject delegate factories for and create them when needed.

These tasks have to be dynamically generated by factories and something like:

var myTaskA = _taskFactoryConcreteTaskA();
var myTaskB = _taskFactoryConcreteTaskB();
var myTaskC = _taskFactoryConcreteTaskC();
...
var myTaskZZ = = _taskFactoryConcreteTaskZZ();

requires a lot of manual wiring (delegates, constructor, backing fields etc) while something like

var myTaskA = _taskFactory.Create<ConcreteTaskA>();
var myTaskB = _taskFactory.Create<ConcreteTaskB>();
var myTaskC = _taskFactory.Create<ConcreteTaskC>();
...
var myTaskZZ = _taskFactory.Create<ConcreteTaskZZ>();

would be incredibly less work especially if the _taskFactory wraps the container as shown in this other post, but also it would basically mean I am using a service locator to create my tasks.

What other options do I have that may be suitable to solve this?

(NOTE: there is a good chance I am completely off track and that I have to read a lot more about DI, in which case any contribution would be even more important)

like image 419
mhttk Avatar asked Nov 10 '11 20:11

mhttk


2 Answers

Since the factories indicated in the question don't take any arguments, using a factory smells of a Leaky Abstraction. As Nicholas Blumhardt points out in his answer, a better approach might be to simply inject each task into the consumer.

In this case, since all the tasks implement the same interface, instead of injecting up to 50 different ITask instances, you can compose them:

public class MyConsumer
{
    private readonly IEnumerable<ITask> tasks;

    public MyConsumer(IEnumerable<ITask> tasks)
    {
        this.tasks = tasks;
    }

    public void DoSomething()
    {
        foreach (var t in this.tasks)
        {
            // Do something with each t
        }
    }
}

Alternatively, you can compose the sequence of ITasks into a Composite, which is actually my preferred solution:

public CompositeTask : ITask
{
    private readonly IEnumerable<ITask> tasks;

    public CompositeTask(IEnumerable<ITask> tasks)
    {
        this.tasks = tasks;
    }

    // Implement ITask by iterating over this.tasks
}

This would simplify the consumer and turn the fact that there are more than one task to be performed into an implementation detail:

public class MyConsumer
{
    private readonly ITask task;

    public MyConsumer(ITask task)
    {
        this.task = task;
    }

    public void DoSomething()
    {
        // Do something with this.task
    }
}
like image 199
Mark Seemann Avatar answered Nov 14 '22 22:11

Mark Seemann


One approach worth investigating is to break the problem into'units of work' that use a set of related tasks:

public class WorkItem1 : ISomeWork
{
    public WorkItem1(Task1 t1, Task2 t2...) { }
    public void DoSomething() { ... }
}

Then, your use of factories would come down towards someWorkFactory().DoSomething(), possibly for a few different kinds of 'something'.

A class having a large number of dependencies, on factories or anything else, usually points to there being smaller, more focused classes waiting to be discovered to break up the work.

Hope this helps.

like image 32
Nicholas Blumhardt Avatar answered Nov 15 '22 00:11

Nicholas Blumhardt