3

I am new to dependency injection pattern. I love the idea, but struggle to apply it to my case. I have a singleton object, let’s call it X, which I need often in many parts of my program, in many different classes, sometimes deep in the call stack. Usually I would implement this as a globally available singleton. How is this implemented within the DI pattern, specifically with .NET Core DI container? I understand I need to register X with the DI container as a singleton, but how then I get access to it? DI will instantiate classes with constructors which will take reference to X, that’s great – but I need X deep within the call hierarchy, within my own objects which .NET Core or DI container know nothing about, in objects that were created using new rather than instantiated by the DI container.

I guess my question is – how does global singleton pattern aligns/implemented by/replaced by/avoided with the DI pattern?

Vitaly
  • 411
  • 1
  • 5
  • 11
  • 1
    can you share your piece of code? –  Dec 05 '18 at 11:53
  • Thanks for asking, but the question is not related to a specific piece of code - it's rather about how/when/why patterns are applicable. – Vitaly Dec 05 '18 at 12:09
  • ok then I will get to know something new today, waiting for someone to answer. :) –  Dec 05 '18 at 12:20
  • There's a really [awesome book](https://manning.com/seemann2) on this subject that I can highly recomend. – Steven Dec 06 '18 at 21:18

3 Answers3

2

Well, "new is glue" (Link). That means if you have new'ed an instance, it is glued to your implementation. You cannot easily exchange it with a different implementation, for example a mock for testing. Like gluing together Lego bricks.

I you want to use proper dependency injection (using a container/framework or not) you need to structure your program in a way that you don't glue your components together, but instead inject them.

Every class is basically at hierarchy level 1 then. You need an instance of your logger? You inject it. You need an instance of a class that needs a logger? You inject it. You want to test your logging mechanism? Easy, you just inject something that conforms to your logger interface that logs into a list and the at the end of your test you can check your list and see if all the required logs are there. That is something you can automate (in contrast to using your normal logging mechanism and checking the logfiles by hand).

That means in the end, you don't really have a hierarchy, because every class you have just gets their dependencies injected and it will be the container/framework or your controlling code that determines what that means for the order of instantiation of objects.


As far as design patterns go, allow me an observation: even now, you don't need a singleton. Right now in your program, it would work if you had a plain global variable. But I guess you read that global variables are "bad". And design patterns are "good". And since you need a global variable and singleton delivers a global variable, why use the "bad", when you can use the "good" right? Well, the problem is, even with a singleton, the global variable is bad. It's a drawback of the pattern, a toad you have to swallow for the singleton logic to work. In your case, you don't need the singleton logic, but you like the taste of toads. So you created a singleton. Don't do that with design patterns. Read them very carefully and make sure you use them for the intended purpose, not because you like their side-effects or because it feels good to use a design pattern.

nvoigt
  • 75,013
  • 26
  • 93
  • 142
  • Thank you for your comment nvoigt. So what you are suggesting is that instead of using new throughout the program I would use the DI container instantiation instead? That would mean I would lose some syntax sugar conveniences, but that's OK. What's more problematic is that in each and every class I would have to keep a reference to the DI container, or have it as a global singleton/variable as Khai Nguyen suggested above. – Vitaly Dec 05 '18 at 15:47
  • Well, I don't know your program, so I cannot say for sure, but the point is that each major class you instantiate with `new` now will be injected. So you don't replace `new` with `ServiceProvider.Getservice<>`, but instead inject that variable for example in the constructor. Nobody but your `main`needs to know the container. Anybody else just requires certain instances in their constructor. – nvoigt Dec 05 '18 at 15:50
  • So that basically boils down to passing a reference to the DI container down the call stack (by any means - as a parameter to methods, or as a parameter to the classes constructors) so that I have it everywhere. This way I will use new for small/rigid/unimportant classes and ServiceProvider.Getservice<> where I need inversion of control. Well, that seems a workable solution, if only a bit excessive. I shall try this. – Vitaly Dec 05 '18 at 16:05
  • Ah, **no**. Maybe as a quick fix for your singleton problem. This is not about the one `new` you have. It's about *all* `new`s you use. You don't replace them by an explicit call to the container. You restructure your code so that you don't need a new instance but instead get passed one in your constructor. Maybe you can add example code and I will change it to something more fitting for DI. – nvoigt Dec 05 '18 at 16:07
  • Obviously, that only goes for classes that have dependencies. If you got 500 integers and you are supposed to make 250 points (x,y) from them, you can use `new` for the Point class. There is no dependencies here. The point class holds no logic. – nvoigt Dec 05 '18 at 16:09
0

Just an idea and maybe I need your thought:

public static class DependencyResolver
{
    public static Func<IServiceProvider> GetServiceProvider;
}

Then in Startup:

public void Configure(IApplicationBuilder app, IServiceProvider serviceProvider)
{
    DependencyResolver.GetServiceProvider = () => { return serviceProvider; };
}

And now in any deed class:

DependencyResolver.GetServiceProvider().GetService<IService>();
Khai Nguyen
  • 935
  • 5
  • 17
0

Here's a simplified example of how this would work without a singleton. This example assumes that your project is built in the following way:

  • the entry point is main
  • main creates an instance of class GuiCreator, then calls the method createAndRunGUI()
  • everything else is handled by that method

So your simplified code looks like this:

// main
// ... (boilerplate)
container = new Container();
gui = new GuiCreator(container.getDatabase(), container.getLogger(), container.getOtherDependency());
gui.createAndRunGUI();
// ... (boilerplate)

// GuiCreator
public class GuiCreator {
    private IDatabase db;
    private ILogger log;
    private IOtherDependency other;
    
    public GuiCreator(IDatabase newdb, ILogger newlog, IOtherDependency newother) {
        db = newdb;
        log = newlog;
        other = newother;
    }
    
    public void createAndRunGUI() {
        // do stuff
    }
}

The Container class is where you actually define which implementations will be used, while the GuiCreator contructor takes interfaces as arguments. Now let's say the implementation of ILogger you choose has itself a dependency, defined by an interface its contructor takes as argument. The Container knows this and resolves it accordingly by instantiating the Logger as new LoggerImplementation(getLoggerDependency());. This goes on for the entire dependency chain.

So in essence:

  • All classes keep instances of interfaces they depend upon as members.
  • These members are set in the respective constructor.
  • The entire dependency chain is thus resolved when the first object is instantiated. Note that there might/should be some lazy loading involved here.
  • The only places where the container's methods are accessed to create instances are in main and inside the container itself:
    • Any class used in main receives its dependencies from main's container instance.
    • Any class not used in main, but rather used only as a dependency, is instantiated by the container and receives its dependencies from within there.
    • Any class used neither in main nor indirectly as a dependency somewhere below the classes used in main will obviously never be instantiated.
  • Thus, no class actually needs a reference to the container. In fact, no class needs to know there even is a container in your project. All they know is which interfaces they personally need.

The Container can either be provided by some third party library/framework or you can code it yourself. Typically, it will use some configuration file to determine which implementations are actually supposed to be used for the various interfaces. Third party containers will usually perform some sort of code analysis supported by annotations to "autowire" implementations, so if you go with a ready-made tool, make sure you read up on how that part works because it will generally make your life easier down the road.

scenia
  • 1,609
  • 14
  • 33