Dependency Injection – Why Encourage Collaboration via Constructors?

dependency-injectionsingleton

The general approach to DI that I see in answers like
So Singletons are bad, then what? encourages
business objects that collaborate with other objects to (a) not
directly create those instances and (b) have them passed in at
construction. I can understand (a), but not (b). This seems to occur
most often in response to overuse of Singletons. But why not just have
a modified approach to singleton:

class SingleInstance {
  virtual foo();
  virtual bar();

  static SingleInstance getInstance() {
    if(instance_ == null) {
      instance_ = new SingleInstance();
    }
    return instance_;
  }

  void setMockInstance(SingleInstance s) {
    assert(instance_ == null);
    instance_ = s;
  }

  static SingleInstance instance_;
}

So now this is no longer a Singleton with a capital S (per Misko
Hevery) but in this case still enforces one instance. All code that
wants to access the single instance can still call S::getInstance()
without cluttering up their constructors by explicitly requiring the
instance to be passed in. The default can still be still lazily
initialized in the production code, but for test can still be mocked.

In the referenced answer the first benefit of DI is listed as:

It makes the code easier to read; you can clearly understand from
the interfaces exactly what data the dependent classes depend on.

But why is that not a violation of encapsulation? Do I really need to
know from the public interface everything that accesses the
SingleInstance/Database/etc?

Assume you have a Database and 30 TableGateway classes responsible
for CRUD operations on those tables. In the DI approach TableGateway
constructors would accept the Database on in its constructor. Then a
business logic class would accept the tables it collaborates with/uses:

class BusinessLogic {
  BusinessLogic(Table1 t1, Table2 t2, Table3 t3);
  void doBusiness() {
    t1_.query(...);
    t2_.insert(...);
    t3_.update(...);
  }
}

How is that churn of explicit dependencies in the constructor
advisable?

Best Answer

Your modified approach to singleton is still a singleton, and has most of the drawbacks of a singleton:

  • It still introduces high coupling across the application - that is, if one day you want to refactor away from singleton, you will have a hard time doing it;
  • It still gives a way for any component to collaborate with the singleton - meaning that potentially, the singleton can communicate with any piece of the system - but if you go for any kind of layered architectural pattern (MVC, MVVM, 3-tier), then the singleton is a point that has to be controlled.

Encapsulation is related to the fact that an object exposes the operations it supports without requiring that clients know about its implementation - but when you invoke a constructor, the object does not exist yet. The constructor is an implementation detail of the object, equivalent to a static method. In other words, once the object is created, the constructor arguments are irrelevant to the operation of the object.

As for the "churn" of explicit dependencies, making the dependencies explicit is one of the goals of constructor injection. If you have too many, it is taken as a sign that a refactoring, or a reorganization, is in order. (E.g. group related tables into a logical group).

Related Topic