23

In the past year, I created a new system using Dependency Injection and an IOC container. This taught me a lot about DI!

However, even after learning the concepts and proper patterns, I consider it a challenge to decouple code and introduce an IOC container into a legacy application. The application is large enough to the point that a true implementation would be overwhelming. Even if the value was understood and the time was granted. Who's granted time for something like this??

The goal of course is to bring unit tests to the business logic!
Business logic that is intertwined with test-preventing database calls.

I've read the articles and I understand the dangers of Poor Man's Dependency Injection as described in this Los Techies article. I understand it does not truly decouple anything.
I understand that it can involve much system wide refactoring as implementations require new dependencies. I would not consider using it on a new project with any amount of size.

Question: Is it okay to use Poor Man's DI to introduce testability to a legacy application and start the ball rolling?

In addition, is using Poor Man's DI as a grass roots approach to true Dependency Injection a valuable way to educate on the need and benefits of the principle?

Can you refactor a method that has a database call dependency and abstract that call to behind an interface? Simply having that abstraction would make that that method testable since a mock implementation could be passed in via a constructor overload.

Down the road, once the effort gains supporters, the project could be updated to implement an IOC container and the constructors would be out there that take in the abstractions.

Airn5475
  • 355
  • 1
  • 2
  • 7

3 Answers3

31

The critique about Poor Man's Injection in NerdDinner has less to do with whether or not you use a DI Container than it does about setting up your classes correctly.

In the article, they state that

public class SearchController : Controller {

    IDinnerRepository dinnerRepository;

    public SearchController() : this(new DinnerRepository()) { }

    public SearchController(IDinnerRepository repository) {
        dinnerRepository = repository;
    }
}

is incorrect because, while the first constructor does provide a convenient fallback mechanism for constructing the class, it also creates a tightly-bound dependency to DinnerRepository.

The correct remedy of course is not, as Los Techies suggests, to add a DI container, but rather to remove the offending constructor.

public class SearchController : Controller 
{
    IDinnerRepository dinnerRepository;

    public SearchController(IDinnerRepository repository) {
        dinnerRepository = repository;
    }
}

The remaining class now has its dependencies properly inverted. You're now free to inject those dependencies however you like.

Robert Harvey
  • 200,592
25

You make a false assumption here over what "poor man's DI" is.

Creating a class that has a "shortcut" constructor that still creates coupling is not poor man's DI.

Not using a container, and creating all injections and mappings manually, is poor man's DI.

The term "poor man's DI" sounds like a bad thing to do. For that reason, the term "pure DI" is encouraged these days as it sounds more positive and actually more accurately describes the process.

Not only is it absolutely fine to use poor man's/pure DI to introduce DI into an existing app, it is also a valid way of using DI for many new applications. And as you say, everyone should use pure DI on at least one project to really understand how DI works, before handling responsibility over to the "magic" of an IoC container.

David Arno
  • 39,599
  • 9
  • 94
  • 129
0

Paragidm shifts in legacy teams/code bases are extremely risky:

Any time you suggest "improvements" to legacy code and there are "legacy" programmers on the team, you are just telling everyone that "they did it wrong" and you become The Enemy for the rest of your/their time with the company.

Using a DI Framework as a hammer to smash all the nails will just make legacy code worse than better in all cases. It is also extremely risky personally.

Even in the most limited cases like just so it can be used in test cases will just make those test cases "non-standard" and "foreign" code that will at best just get marked @Ignore when they break or worse get complained about constantly by the legacy programmers with the most clout with management and get re-written "correctly" with all this "wasted time on unit tests" blamed solely on you.

Introducing a DI framework, or even the concept of "Pure DI" to a line of business app, much less a huge legacy code base without the by off of management, the team and especially sponsorship of the lead developer will just be the death knell for you socially and politically on the team/company. Doing things like this is extremely risky and can be political suicide in the worst way.

Dependency Injection is a solution looking for a problem.

Any language with constructors by definition uses dependency injection by convention if you know what you are doing and understand how to properly use a constructor, this is just good design.

Dependency injection is only useful in small doses for a very narrow range of things:

  • Things that change a lot or have lots of alternative implementations that are statically bound.

    • JDBC drivers are a perfect example.
    • HTTP clients that might vary from platform to platform.
    • Logging systems that vary by platform.
  • Plugin systems that have configurable plugins that can be defined in configuration code in your framework and discovered automatically at startup and dynamically loaded/reloaded while the program is running.