Archives February 2024

Code smell – Control Freak – Dependency Injection

Control freak is a code smell and even an anti-pattern that forbids us from using the new keyword. Yes, using the new keyword is the code smell! The following code is wrong and can’t leverage DI:

namespace CompositionRoot.ControlFreak;
public class Consumer
{
    public void Do()
    {
        var dependency = new Dependency();
        dependency.Operation();
    }
}
public class Dependency
{
    public void Operation()
        => throw new NotImplementedException();
}

The highlighted line shows the anti-pattern in action. To enable the Consumer class to use dependency injection, we could update it like the following:

public class Consumer
{
    private readonly Dependency _dependency;
    public DIEnabledConsumer(Dependency dependency)
    {
        _dependency = dependency;
    }
    public void Do()
    {
        _dependency.Operation();
    }
}

The preceding code removes the new keyword and is now open for modification. The highlighted lines represent the constructor injection pattern we explore subsequently in this chapter.Nevertheless, do not ban the new keyword just yet. Instead, every time you use it, ask yourself whether the object you instantiated using the new keyword is a dependency that could be managed by the container and injected instead.To help with that, I borrowed two terms from Mark Seemann’s book Dependency Injection in .NET; the name Control Freak also comes from that book. He describes the following two categories of dependencies:

  • Stable dependencies
  • Volatile dependencies

Next is my take on defining them.

Stable dependencies

Stable dependencies should not break our application when a new version is released. They should use deterministic algorithms (input X should always produce output Y), and you should not expect to change them with something else in the future.

Most data structures devoided of behaviors, like Data Transfer Objects (DTOs), fall into this category. You can also consider the .NET BCL as stable dependencies.

We can still instantiate objects using the new keyword when they fall into this category because the dependencies are stable and unlikely to break anything if they change.Next, we look at their counterpart.

Volatile dependencies

Volatile dependencies can change at runtime, like extendable elements with contextual behaviors. They may also be likely to change for various reasons like new features development.

Most classes we create, such as data access and business logic code, are volatile dependencies.

The primary way to break the tight coupling between classes is to rely on interfaces and DI and no longer instantiate those volatile dependencies using the new keyword. Volatile dependencies are why dependency injection is key to building flexible, testable, and maintainable software.

Conclusion

To conclude this interlude: don’t be a control freak anymore; those days are behind you!

When in doubt, inject the dependency instead of using the new keyword.

Next, we explore the available lifetimes we can attribute to our volatile dependencies.

Object lifetime

Now that we understand we should no longer use the new keyword, we need a way to create those classes. From now on, the IoC container will play that role and manage object instantiation and their lifetime for us.