Avoiding Service Locator Antipattern with legacy app not designed for IOC

前端 未结 1 420
长情又很酷
长情又很酷 2021-02-03 10:12

I have read often that Service Locators in IOC are an anti-pattern.

Last year we introduced IOC (Ninject specifically) to our application at work. The app is legacy, it

相关标签:
1条回答
  • 2021-02-03 10:36

    So the ideal way of doing it from what I have read is injecting a kernel whenever you need to access IOC...well that's all fine and good; we do keep the use of the SL to a minimum.

    No, injecting the kernel into your business classes is not the best way to go. The better way is to create a factory e.g. IFooFactory { IFoo Create(); } or Func<IFoo> and let this one create the new instance.

    The implementation of this interface goes into the composite root, gets an instance of the kernel and does the resolve using the kernel. This keeps your classes free of a specific container and you can reuse them in another project using a different container. In case of Func you can use the following module: Does Ninject support Func (auto generated factory)? Ninject 2.4 will have native support for this.


    As far as the refactoring goes, it is hardly possible to tell you what's the best way to go without knowing the source code of your application. I can just give you an approch that probably can work.

    I suppose you want to refactor the whole application to proper DI in long term. What I did once for a quite large project (30-40 man-years) was about the following:

    Start at the composite root(s) and work down the object tree and change one class after the other to use proper DI. Once you reached all leafs start to refactor all the services that do not depend on other services and work to their leafs using the same approach. Afterwards, continue with the services that depend only on services that have already been refactored and repeat until all services are refactored. All these steps can be done one after the other so that the code continously gets improved while new features can still be added at the same time. In the mean time ServiceLocation is acceptable, as long as the focus is to get it right as soon as possible.

    Pseudo code example:

    Foo{ ServiceLocator.Get<Service1>(), new Bar() }
    Bar{ ServiceLocator.Get<IService1>(), ServiceLocator.Get<IService2>(), new Baz() }
    Baz{ ServiceLocator.Get<IService3>() }
    Service1 { ServiceLocator.Get<Service3>() }
    Service2 { ServiceLocator.Get<Service3>() }
    Service3 { new SomeClass()}
    

    Step 1 - Change Root (Foo)

    Foo{ ctor(IService1, IBar) }
    Bar{ ServiceLocator.Get<IService1>(), ServiceLocator.Get<IService2>(), new Baz() }
    Baz{ ServiceLocator.Get<IService3>() }
    Service1 { ServiceLocator.Get<IService2>() }
    Service2 { ServiceLocator.Get<IService3>() }
    Service3 { new SomeClass()}
    
    Bind<IBar>().To<Bar>();
    Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());
    

    Step 2 - Change dependencies of root

    Foo{ ctor(IService1, IBar) }
    Bar{ ctor(IService1, IService2, IBaz) }
    Baz{ ServiceLocator.Get<IService3>() }
    Service1 { ServiceLocator.Get<Service2>() }
    Service2 { ServiceLocator.Get<Service3>() }
    Service3 { new SomeClass()}
    
    Bind<IBar>().To<Bar>();
    Bind<IBaz>().To<Baz>();
    Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());
    Bind<IService2>().ToMethod(ctx => ServiceLocator.Get<IService2>());
    

    Step 3 - Change their dependencies and continue until you are at the leafs

    Foo{ ctor(IService1, IBar) }
    Bar{ ctor(IService1, IService2, IBaz) }
    Baz{ ctor(IService3) }
    Service1 { ServiceLocator.Get<Service2>() }
    Service2 { ServiceLocator.Get<Service3>() }
    Service3 { new SomeClass() }
    
    Bind<IBar>().To<Bar>();
    Bind<IBaz>().To<Baz>();
    Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());
    Bind<IService2>().ToMethod(ctx => ServiceLocator.Get<IService2>());
    Bind<IService3>().ToMethod(ctx => ServiceLocator.Get<IService3>());
    

    Step 4 - Refactor the services that do not depend on other ones

    Foo{ ctor(IService1, IBar) }
    Bar{ ctor(IService1, IService2, IBaz) }
    Baz{ ctor(IService3) }
    Service1 { ServiceLocator.Get<Service2>() }
    Service2 { ServiceLocator.Get<Service3>() }
    Service3 { ctor(ISomeClass) }
    
    Bind<IBar>().To<Bar>();
    Bind<IBaz>().To<Baz>();
    Bind<ISomeClass>().To<SomeClass>();
    Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());
    Bind<IService2>().ToMethod(ctx => ServiceLocator.Get<IService2>());
    Bind<IService3>().To<Service3>().InSingletonScope();
    

    Step 5 - Next refactor those that depend on services that have only refactored services as dependency.

    Foo{ ctor(IService1, IBar) }
    Bar{ ctor(IService1, IService2, IBaz) }
    Baz{ ctor(IService3) }
    Service1 { ServiceLocator.Get<Service2>() }
    Service2 { ctor(IService3) }
    Service3 { ctor(ISomeClass) }
    
    Bind<IBar>().To<Bar>();
    Bind<IBaz>().To<Baz>();
    Bind<ISomeClass>().To<SomeClass>();
    Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());
    Bind<IService2>().To<Service2>().InSingletonScope();
    Bind<IService3>().To<Service3>().InSingletonScope();
    

    Step 6 - Repeat until every service is refactored.

    Foo{ ctor(IService1, IBar) }
    Bar{ ctor(IService1, IService2, IBaz) }
    Baz{ ctor(IService3) }
    Service1 { ctor(IService2) }
    Service2 { ctor(IService3) }
    Service3 { ctor(ISomeClass) }
    
    Bind<IBar>().To<Bar>();
    Bind<IBaz>().To<Baz>();
    Bind<ISomeClass>().To<SomeClass>();
    Bind<IService1>().To<Service1>().InSingletonScope();
    Bind<IService2>().To<Service2>().InSingletonScope();
    Bind<IService3>().To<Service3>().InSingletonScope();
    

    Probably you want to switch to a convention based container configuration together with the refactoring. In this case I'd add an attribute to all refactored classes to mark them and remove it again after all the refactoring is done. In the conventions you can use this attribute to filter for all the refactored classes.

    0 讨论(0)
提交回复
热议问题