-1

Given the (old) debate over whether Singletons are overused/abused/are worth it - would it be a bad idea to inject the dependencies as default parameters? In this way, we could get rid of defining singletons for the dependency container registry for the corresponding interfaces - while still leaving the objects in question testable - all one needs to do is to explicitly inject a mock as the dependency when Unit Testing such classes.

Example:

public class Foo
{
    IDoSomething x;
    Foo(IDoSomething x = null)
    {
        this.x = x ?? new DoSomething()
    }
}

public class DoSomething : IDoSomething
{
   ...
}

in Program.cs
services.AddSingleton(IDoSomething, DoSometing);

This way I am rid of holding singletons throughout my app, without losing testability.

public class FooTests
{
    Foo SUT;
    Mock<Foo> FooMock;
    FooTests() 
    {
        FooMock = new Mock<Foo>();
        FooMock.Setup(...);
        SUT = new Foo(FooMock.Object);
    }
}

Giving it a second thought, if I require a singleton (if I need to assure no 2 instances exist for this object) than it's more like a matter of defining the lifetime of that instance (singleton, scoped, transaction). In other words, there should be nothing wrong in injecting a dependency as a mandatory parameter - and properly registering the instantiation type at the dependency container.

3
  • What do you do if the DoSomething constructor itself wants some arguments? May 24 at 7:03
  • @SebastianRedl: you mean it itself has external type dependencies ? yes I would be in trouble...
    – Veverke
    May 24 at 8:11
  • 2
    You've now tightly coupled the Foo and DoSomething classes, which is what the IDoSomething interface is intended to solve. If you absolutely need a way to tie those two classes together, introduce a third mediator class (sometimes implemented as a factory). May 26 at 16:09

2 Answers 2

0

There's no singleton in your example, but what you are doing isn't bad per se.

Its fairly common practice to provide default implementations where there is going to be tight coupling regardless. Obviously you are not quite as decoupled, but as long as the default class is small the downside is small or nothing and you gain convenience of use.

1
  • I had in mind registering the dependency in the Dependency Container as a singleton. Added that line under "in Program.cs".
    – Veverke
    May 24 at 5:24
0

In any reasonably sized codebase, the nested DI parameters are going to explode, which is going to bite you when you want to write those concrete constructors like that. Even if reasonable on a whiteboard, tyour idea does not pass the smell test in actual production code.

Based on that alone, it's probably worth letting go of this idea.

If you're going to be using singletons (which I advise against but never mind that), using a DI container is a better way to do so.

However, that doesn't explain why you're trying to make these optional parameters. What would be the benefit of deciding the concrete type DoSomething in the constructor, as opposed to deciding it when registering your DI container?

If multiple classes depend on the same dependency, you have to repeat the same concrete class over and over.

I don't see any upside to this. It seems to me that the only thing you're achieving is that you sidestep the DI container in favor of manually mapping (and tightly coupling) the concrete dependency types. That's a step in the wrong direction.

2
  • The motif behind my idea is: writing testable objects, without the need to rely on DI containers. In other words, each object will figure out this way or another how to construct its dependency - while the object will still be testable - because I left the door open to pass a mock as the dependency.
    – Veverke
    May 24 at 15:01
  • @Veverke: You can still manually inject dependencies during tests when the dependencies are not optional (this is the preferred way anyway). The optional nature of your injected dependencies is allowing you to not have to inject your dependencies, which according to your code happens when you want a real dependency. This makes no sense in the real codebase as the real DI container would already be handling the injection for you (plus the complexity of many layers of dependencies needing to be instantiated), and you'd already be manually passing in mocks during your tests.
    – Flater
    May 24 at 18:12

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.