Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid Service Locator Anti-Pattern?

I'm trying to remove a Service Locator from an abstract base class, but I'm not sure what to replace it with. Here is a psuedo-example of what I've got:

public abstract class MyController : Controller
{
    protected IKernel kernel;
    public MyController(IKernel kernel) { this.kernel = kernel); }

    protected void DoActions(Type[] types)
    {

        MySpecialResolver resolver = new MySpecialResolver(kernel);
        foreach(var type in types)
        {
            IMyServiceInterface instance = resolver.Get(type);
            instance.DoAction();
        }
    }
}

The problem with this is that the instanciator of a derived class doesn't know what bindings the kernel must have in order to keep MySpecialResolver from throwing an exception.

This might be intrinsicly intractable because I don't know from here which types I'll have to resolve. The derived classes are responsible for creating the types parameter, but they aren't hardcoded anywhere. (The types are based on the presence of attributes deep within the derived class's composition hierarchy.)

I've trying to fix this with lazy loading delegates, but so far I haven't come up with a clean solution.

Update

There are really two issues here, one is that the IoC container is passed to the controller, acting as a service locator. This is easy to remove--you can move the location up or down the call stack using all sorts of techniques.

The second issue is the difficult one, how can you ensure that the controller has the necessary services when the requirements aren't exposed until runtime. It should have been obvious from the start: you can't! You will always be dependent upon either the state of the service locator or contents of a collection. In this particular case no amount of fiddling will ever resolve the problem described in this article with staticly typed dependencies. I think that what I'm going to end up doing is passing a Lazy array into the controller constructor and throwing an exception if a required dependency is missing.

like image 688
Paul Avatar asked Jul 26 '11 17:07

Paul


People also ask

Is Service Locator a singleton?

All components need to have a reference to the service locator, which is a singleton. The service locator makes the application hard to test. A service locator makes it easier to introduce breaking changes in interface implementations.

What's the difference between the dependency injection and Service Locator patterns?

The main difference is how the dependencies are located, in Service Locator, client code request the dependencies, in DI Container we use a container to create all of objects and it injects dependency as constructor parameters (or properties). Dependency Injection doesn't require the use of a DI Container though.

What is Service Locator in C#?

The Service Locator is a pattern by which we can reduce the dependency of one object on another that we will see shortly and Dependency injection (DI) is another smart solution for the same problem.

What is Service Locator design pattern in Java?

The service locator pattern is a design pattern used in software development to encapsulate the processes involved in obtaining a service with a strong abstraction layer. This pattern uses a central registry known as the “service locator” which on request returns the information necessary to perform a certain task.


2 Answers

I agree with @chrisichris and @Mark Seemann.

Ditch the kernel from the controller. I'd switch your resolver composition a little bit so that your controller can remove the dependency on the IoC container and allow the resolver to be the only item that worries about the IoC container.

Then I would let the resolver get passed into the constructor of the controller. This will allow your controller to be far more testable.

For example:

public interface IMyServiceResolver
{
    List<IMyServiceInterface> Resolve(Type[] types);
}

public class NinjectMyServiceResolver : IMyServiceResolver
{
    private IKernal container = null;

    public NinjectMyServiceResolver(IKernal container)
    {
        this.container = container;
    }

    public List<IMyServiceInterface> Resolve(Type[] types)
    {
        List<IMyServiceInterface> services = new List<IMyServiceInterface>();

        foreach(var type in types)
        {
            IMyServiceInterface instance = container.Get(type);
            services.Add(instance);
        }

        return services;
    }
}

public abstract class MyController : Controller
{
    private IMyServiceResolver resolver = null;

    public MyController(IMyServiceResolver resolver) 
    { 
        this.resolver = resolver;
    }

    protected void DoActions(Type[] types)
    {
        var services = resolver.Resolve(types);

        foreach(var service in services)
        {
            service.DoAction();
        }
    }
}

Now your controller isn't coupled to a specific IoC container. Also your controller is much more testable since you can mock the resolvers and not require an IoC container at all for your tests.

Alternatively, if you don't get to control when a controller is instantiated, you can modify it slightly:

public abstract class MyController : Controller
{
    private static IMyServiceResolver resolver = null;

    public static InitializeResolver(IMyServiceResolver resolver)
    {
        MyController.resolver = resolver;
    }

    public MyController() 
    { 
        // Now we support a default constructor
        // since maybe someone else is instantiating this type
        // that we don't control.
    }

    protected void DoActions(Type[] types)
    {
        var services = resolver.Resolve(types);

        foreach(var service in services)
        {
            service.DoAction();
        }
    }
}

You would then call this at your application start up to initialize the resolver:

MyController.InitializeResolver(new NinjectMyServiceResolver(kernal));

We did this to handle elements created in XAML who require dependencies resolved but we wanted to remove Service Locator like requests.

Please excuse any syntactical errors :)

I'm writing a blog post series on the topic of refactoring an MVVM application with Service Locator calls in the view models you might find interesting. Part 2 is coming soon :)

http://kellabyte.com/2011/07/24/refactoring-to-improve-maintainability-and-blendability-using-ioc-part-1-view-models/

like image 159
Kelly Sommers Avatar answered Oct 03 '22 21:10

Kelly Sommers


Maybe you should just do away the Kernel, Types and MySpecialResolver and let the subclasses call DoActions with the IMyServiceInterface instances they need as argument directly. And let the subclasses decide how they get to these instances - they should know best (or in case they don't know which exactly the one who ever decides which instances of IMyServiceInterface are needed)

like image 35
chrisichris Avatar answered Oct 03 '22 21:10

chrisichris