The Problem
http://www.devtrends.co.uk/blog/how-not-to-do-dependency-injection-the-static-or-singleton-container
How we can go about fixing it
Scenario 1
Here is an simplified example of a static service class code smell leading to the anti-pattern: below is a static method providing some sort of service.namespace StaticServiceLocator
- public class Consumer
- {
- public void DoSomething()
- {
- // do some work
- var fuel = new Fuel();
- var smoke = SmokeService.MakeSmoke(fuel);
- // do some more work
- }
- }
Here is the culprit: A static service class.
- namespace StaticServiceLocator
- {
- public class SmokeService
- {
- public static Smoke MakeSmoke(Fuel fuel)
- {
- return fuel.Burn();
- }
- }
- }
- One internal master constructor rule
In my experience, having one internal master constructor with all the class dependencies responsible for setting state is the way to go in terms of maintainability and flexibility. The master constructor should be the ONLY constructor that sets state in the class. If there is a need to instantiate the class from the consumer you can add public constructors that call the master constructor. You can then test the class if you make your test assembly a friend assembly (using the InternalsVisibleTo assembly attribute) but can control the public surface and keep it simple for consumers. If the class to too complex for a master constructor, it is time to look into a factory or refactoring the class.
- Define an internal interface if the service is a static class
Add the new interface to the internal master constructor and create an overloaded public constructor with the original contract so that we don’t break any consumers.
- namespace StaticServiceLocator
- {
- public class Consumer
- {
- private ISmokeService _SmokeService;
- internal Consumer(ISmokeService smokeService)
- {
- _SmokeService = smokeService;
- }
- public void DoSomething()
- {
- // do some work
- var fuel = new Fuel();
- var smoke = _SmokeService.MakeSmoke(fuel);
- // do some more work
- }
- }
- }
- Refactor the static service class to be non-static and have it implement the interface
- namespace StaticServiceLocator
- {
- public class SmokeService : ISmokeService
- {
- public Smoke MakeSmoke(Fuel fuel)
- {
- return fuel.Burn();
- }
- }
- }
- Implement the overloaded constructor(s)
- namespace StaticServiceLocator
- {
- public class Consumer
- {
- private ISmokeService _SmokeService;
- internal Consumer(ISmokeService smokeService)
- {
- _SmokeService = smokeService;
- }
- public Consumer() : this(new SmokeService()) { }
- public void DoSomething()
- {
- // do some work
- var fuel = new Fuel();
- var smoke = _SmokeService.MakeSmoke(fuel);
- // do some more work
- }
- }
- }
Now we are able to isolate the consumer class code for testing, have not changed the public surface and shouldn’t have broken any consumers.
Scenario 2
Sometimes there will be an IOC framework providing a static service locator. This is still a smell, but much less invasive to clean up because we are already working with an abstraction for the service class.
- namespace StaticServiceLocator
- {
- public class Consumer2
- {
- public void DoSomething()
- {
- // do some work
- var fuel = new Fuel();
- var smoke = ServiceLocator.GetService<ISmokeService>().MakeSmoke(fuel);
- // do some more work
- }
- }
- }
This issue is a bit easier to fix in that we just have to modify this class by creating the internal constructor in order to do dependency injection and move the service locator up to the overloaded public constructor so that we don’t change the public surface and can isolate our consumer class code for unit testing. Eventually moving to an IOC framework to provide DI we can safely refactor the behavior of the new constructor.
- namespace StaticServiceLocator
- {
- public class Consumer2
- {
- private readonly ISmokeService _SmokeService;
- internal Consumer2(ISmokeService smokeService)
- {
- _SmokeService = smokeService;
- }
- public Consumer2() : this(ServiceLocator.GetService<ISmokeService>()) { }
- public void DoSomething()
- {
- // do some work
- var fuel = new Fuel();
- var smoke = _SmokeService.MakeSmoke(fuel);
- // do some more work
- }
- }
- }
Gowtham RajamanickamPosted Apr 2, 2015, 7:12 AM
great work
Santhakumar MunuswamyPosted Apr 2, 2015, 12:06 AM
Good work
Tom MohanPosted Apr 1, 2015, 11:26 AM
good
Karthik Muthu KaruppanPosted Apr 1, 2015, 11:23 AM
Good one
NitinPosted Apr 1, 2015, 9:22 AM
Nice