9

I had something of a convenient service locator anti-pattern in a previous game project. I'd like to replace this with dependency injection. autofac looks like the most likely DI container for me as it seems to have relevant features - but I can't figure out how to achieve what I'm looking for.

Existing Approach

Rather than a single service locator, I had a service locator which could delegate to its parent (in effect providing "scoped" services):

class ServiceLocator {
    ServiceLocator _parent;
    Dictionary<Type, object> _registered = new Dictionary<Type, object>();

    public ServiceLocator(ServiceLocator parent = null) {
        _parent = parent;
    }

    public void Register<T>(T service) {
        _registered.Add(typeof(T), service);
    }

    public T Get<T>() {
        object service;
        if (_registered.TryGetValue(typeof(T), out service)) {
            return (T)service;
        }
        return _parent.Get<T>();
    }
}

Simplifying for clarity, the game consisted of a tree of Component-derived classes:

abstract class Component {
    protected ServiceLocator _ownServices;
    protected List<Component> _components = new List<Component>();
    ...

    public Component(ServiceLocator parentServices) {
        _ownServices = new ServiceLocator(parentServices);
    }

    ...
}

So I could (and did) build tree structures like:

Game
 -  Audio : IAudioService
 -  TitleScreen : Screen
 -  GameplayScreen : Screen
      -  ShootingComponent : IShootingService
      -  NavigationComponent : INavigationService
     |-  AIComponent (uses IAudioService and IShootingService and INavigationService)

And each component could simply call the ServiceLocator with which it's constructed to find all the services it needs.

Benefits:

  • Components don't have to care who implements the services they use or where those services live; so long as those services' lifetimes are equal to or greater than their own.

  • Multiple components can share the same service, but that service can exist only as long as it needs to. In particular, we can Dispose() a whole portion of the hierarchy when the player quits a level, which is far easier than having components rebuild complex data structures to adjust to the idea that they're now in a completely new level.

Drawbacks:

  • As Mark Seeman points out, Service Locator is an Anti-Pattern.

  • Some components would instantiate service providers purely because I (the programmer) know that nested components need that service, or I (the programmer) know that the game has to have e.g. AI running in the game world, not because the instantiator requires that service per se.

Goal

In the spirit of DI, I would like to remove all knowledge of "service locators" and "scopes" from Components. So they would receive (via DI) constructor parameters for each service they consume. To keep this knowledge out of the components, the composition root will have to specify, for each component:

  • Whether instantiating a specific type of component creates a new scope
  • Within that scope, which services are available.

I want to write the intuitive:

class AIComponent
{
    public AIComponent(IAudioService audio, IShootingService shooting, INavigationService navigation)
    {
        ...
    }
}

And be able to specify in the composition root that

  • IAudioService is implemented by the Audio class and you should create/obtain a singleton (I can do this!)
  • IShootingService is implemented by ShootingComponent and there should be one of those created/obtained per Screen
  • INavigationService as per IShootingService

I must confess I'm completely lost when it comes to the latter two. I won't list my numerous abortive autofac-based attempts here as I've made a few dozen over a long period and none of them were remotely functional. I have read the documentation at length - I know lifetime scopes and Owned<> are in the area I'm looking at, but I can't see how to transparently inject scoped dependencies as I'm looking to - yet I feel that DI in general seems supposed to facilitate exactly what I'm looking to do!

If this is sane, how can I achieve this? Or is this just diabolical? If so, how would you structure such an application making good use of DI to avoid passing objects around recursively, when the lifetimes of those objects vary depending on the context in which the object is being used?

El Zorko
  • 3,349
  • 2
  • 26
  • 34
  • From what I understand you need to implement custom scopes for some of your entities. Is that what you want? – kaptan Dec 26 '13 at 17:31
  • Not certain I'm even close here but perhaps: Resolving some interfaces = searching up through extant scopes to find an existing object implementing IFoo, throwing an exception if not found. (Audio is a singleton somewhere.) Resolving some other interfaces = fetching an impl from a specific scope, creating it in *that* scope if not present. (AI belongs to the "game world" scope.) With some scopes auto-created in a 1:1 relationship with certain objects. (So GameplayScreen implicitly get a game world scope.) With all of the above arranged by the composition root, invisible to domain objects. – El Zorko Dec 26 '13 at 18:15
  • 2
    This sounds similar to the problem of creating objects per web request. I don't know about AutoFac, but Simple Injector (which is also one of the fastest DI containers) has abstracted lifetime and has a plugin for the per-web request lifetime. You may want to look for existing implementations like that to see how they are done. – Matt Miller Jan 06 '14 at 23:04

2 Answers2

1

LifetimeScopes sound like the answer. I think what you are basically doing is tying lifetime scopes to screens. So ShootingComponent and friends would be registered with .InstancePerMatchingLifetimeScope("Screen"). The trick is then making it so that each screen is created in a new LifetimeScope tagged as "Screen." My first thought would be to make a screen factory like so:

public class ScreenFactory
{
    private readonly ILifetimeScope _parent;

    public ScreenFactory(ILifetimeScope parent) { _parent = parent; }

    public TScreen CreateScreen<TScreen>() where TScreen : Screen
    {
        var screenScope = _parent.BeginLifetimeScope("Screen");
        var screen = screenScope.Resolve<TScreen>();
        screen.Closed += () => screenScope.Dispose();
        return screen;
    }
}

This is totally untested, but I think the concept makes sense.

Jim Bolla
  • 8,265
  • 36
  • 54
1

Coincidentally currently I am working with similar requirements (Using Autofac) and here is what I came up with so far:

  • First of all start using modules. They are an excellent way of managing your dependencies and configuration.
  • Define your dependencies with appropriate lifetimes: IAudioService as singleton and IShootingService as per lifetime scope.
  • Make sure your lifetime scoped interfaces also implements IDisposable to ensure proper clean up.
  • Create a thin wrapper to manage all your lifetime in a simple embedded framework: sandwich your game levels between Begin() and End() methods. (this is how I did it. I'm sure you can find a better way of doing this in your own structure)
  • (optional) Create a 'core' module to keep your generic dependencies (i.e. IAudioService) and separate modules for other islands of dependencies (which might be depending on different implementations of the same interface for example)

Here is an example of how I did it:

public ScopedObjects(ILifetimeScope container, IModule module)
{
    _c = container;
    _m = module;
}

public void Begin()
{
    _scope = _c.BeginLifetimeScope(b => b.RegisterModule(_m));
}

public T Resolve<T>()
{
    return _scope.Resolve<T>();
}

public void End()
{
    _scope.Dispose();
}

In the example you gave, I would put the resolution of AIComponent between Begin and End calls of the above class when going through levels.

As I said, I'm sure you will be able to come up with a better way of doing it in your development structure, I hope this gives you the basic idea as to how you can implement it, assuming my experience is to be considered a 'good' way of doing this.

Good luck.

mtmk
  • 6,176
  • 27
  • 32