1

I have a DeviceManager class which can handle different physical devices, and to say there are Classes A,B,C, which require a DeviceManager instance as dependency. They should always use the same instance of DeviceManager, however, at some point of time this DeviceManager should create a new instance in order to wrap a different physical device. So in this case, in terms of maintainability and testability, should I use a static wrapper or dependency injection? I have two solutions, but don't know which is better, or maybe there is a way better solution.

Static Wrapper:

    public static class DeviceService
    {
        static IDeviceManager _deviceManager;

        public static void Handle() { _deviceManager.Handle(); }

        public static void Reset()
        {
            _deviceManager = new Container.Resolve<IDeviceManager>();
        }
    }

Class A,B,C instances then can use DeviceService.Handle() and at some point of time to use DeviceService.Reset() to reassign a new instance.

Dependency Injection

public ClassA(IDeviceManager dm);

public ClassB(IDeviceManager dm);

public ClassC(IDeviceManager dm);

Assume that if I reassign the DeviceManager, then I will also create new Class A,B,C instances.

Static wrapper is handy, and easy to manage the state, but it relies on Service Locator, which is an implicit dependency since it is not passed and referenced by parameters, would it be a pain in unit testing? And I need to call Reset() explicitly before I use/reuse it. Dependency Injection seems to have lower coupling and it is code agnostic, it can even handle using various DeviceManager instances in the future, but my project has many classes and instances relying on DeviceManager, I need to pass an instance to each of them additionally.

I still cannot decide on which one is better, and why.

  • 2
    The whole "at some point on time" the entire device changes seems unworkable. I'm a client thread(?) in the middle of communicating with Fred and mid-conversation I'm talking to Susan? – user949300 Mar 19 at 5:54
  • for example I have a setup which includes three physical devices, I need to iterate each device and do something by using DeviceManager. @user949300 – Haoyuan Tang Mar 19 at 6:14
  • Can you pass a factory as a dependency, and just ask the factory to create a new device manager on Reset? Don't know enough about the system to decide if that's a good idea - you'll have to judge that for yourself - but if you do end up implementing something like that, another point to think about is how will the factory decide on the concrete type to create. The decision logic could be somehow hardcoded in the implementation of the factory itself, or it could be configurable, passed in as an explicit dependency/parameter to the factory. – Filip Milovanović Mar 19 at 6:53
  • Or, if possible, just create several different device manager instances (one for each device type - if you know them in advance, at some point in the code where you can set the whole thing up) and iterate over those directly. – Filip Milovanović Mar 19 at 6:53
  • 1
    "for example I have a setup which includes three physical devices, I need to iterate each device and do something by using DeviceManager". public ClassA(IList<IDeviceManager> deviceManagers). Problem solved. – David Arno Mar 19 at 11:43
1

So many people is confused about Dependency Injection(DI). DI doesn't lead you to use Service Locator. This is just a way of this. You can also define parameter-less constructor and manage all the class from caller without using Service Locator. By this way, first usage class may initialize Device Manager and can pass all others.

public class A
{
    private readonly IDeviceManager _deviceManager;
    public A() : this(new DeviceManager())
    {

    }
    public A(IDeviceManager deviceManager)
    {
        _deviceManager = deviceManager;
    }

    public void SomeFunction()
    {
        B b = new B(_deviceManager);
        C c = new C(_deviceManager);
    }

}

public class B
{
    private readonly IDeviceManager _deviceManager;

    public B(IDeviceManager deviceManager)
    {
        _deviceManager = deviceManager;
    }

    //...
}

public class C
{
    private readonly IDeviceManager _deviceManager;

    public C(IDeviceManager deviceManager)
    {
        _deviceManager = deviceManager;
    }

    //...
}

Look at SomeFunction() in A class. You can use it same way for another class and manage it from caller.

public void CallerFunction1()
{
    // letting A class to initialize DeviceManager
    A a = new A();
    a.SomeFunction();
}

public void CallerFunction2()
{
    // you can manage by creating it and passing all others.        
    IDeviceManager deviceManager = new DeviceManager();
    A a = new A(deviceManager);
    a.SomeFunction();
}

If there is a way of doing something by DI, then choose it. Singleton is good but even that, can not compare with DI.

Edit: I assume you use IoC container as Service Locator because you resolve object from Container. If not, you can create your own Service Locator and manage your dependencies. But, this is not always easy to use way. Because, for example, if you try to store and get DBContext from Service Locator, you need to manage life style strategy(for SimpleInjector Transient, Scoped and Singleton) and many things you would face with. Thus, you can manage them by creating and initializing from caller function and pass other class(without using Service Locator) or let top class to initialize dependencies from its constructor like A class(by doing this, client does not need to know what should be initialize. It just consumes).

Edit 2: A class parameter-less constructor call its constructor by @Greg Burghardt comment.

Edit 3: By considering question on comment :

...I have a class App as the entry point, which will resolve the dependencies DA, DB, DC required respectively by class A,B,C. Then how should I use DI and design the class constructors? A(DA, DB, DC) ->B(DB, DC) -> C(DC)?

If some of your class dependencies are different or all class dependencies have its own special type, then you can call them by initializing dependency from caller and passing parameter or call them by parameter-less constructor(you can add parameter-less constructor to any class if needed).

If a dependency is needed for a class(for example A) inject it by constructor from there(in A constructor). Otherwise, do not inject inner class dependencies to top class. Because, you can not manage entire thing from top class.

By following this way, you can change easily all parameter-less constructor by Service Locator.

  • 1
    The potential issue with the proposed solution is that it often leads to separate constructors for separate use cases, e.g. a "mock" constructor and a "real" constructor. This starts defeating the purpose of DI and unit testing as the test code path has partially diverged from the production code path. I'm not saying this always leads to issues, but it's a "straw that broke the camel's back" situation. It would be a much better approach to only have DI-friendly constructors, which guarantess that you use the exact same logic in your test and production runtimes. – Flater Mar 19 at 9:08
  • Of course it is and I just try to show some way of this. Also there is no need to create parameter-less constructor for B and C class and even A class. It is better to create factory method Service Locator but, as I said before, I try to show way of that using Service Locator (coming from Container) is not mandatory. – Engineert Mar 19 at 9:15
  • I removed redundant parameter-less constructor from B and C class and add Edit Note. – Engineert Mar 19 at 10:59
  • 2
    @Flater: There is nothing wrong with having a "paremeter-less" constructor, as long is it ultimately calls the same constructor that your tests or "DI" uses. The A() constructor should just call the A(IDeviceManager) constructor. That way your test code is your production code. – Greg Burghardt Mar 19 at 11:22
  • @GregBurghardt: Having the class define its own dependencies (regardless of whether it's the mock, real, or both) defeats the purpose of dependency injection where that responsibility is passed to whoever consumes (an object of) that class. – Flater Mar 19 at 11:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.