3

Is this bad when using dependency injection:

public function __construct($service = null)
{
    if(null === $service){
        $service = MyNewDefaultService()
    }
    $this->service = $service;
}

i.e. the notion of having a default fallback class type for services

StuartLC
  • 104,537
  • 17
  • 209
  • 285
user1572427
  • 259
  • 1
  • 3
  • 7

1 Answers1

3

This pattern will work (in fact, the (anti)pattern has a name - Bastard Injection), but there are issues relating to this approach:

  1. By constructing a new MyNewDefaultService() dependency in your consumer class, you are coupling to a concrete service class, in addition to the abstraction. In compiled languages, this would also mean that your consuming code now needs a hard 'reference' to the jar / library / dll / assembly containing the concrete dependency class, whereas if you omit the direct construction you could instead just be coupled on interface only. In scripted languages you would need to ensure the concrete dependency is resolvable at runtime.

  2. The lifespan management of the Dependency MyNewDefaultService is now hard-coded to be the same as the life of the consuming class. Lifespans of objects injected and managed by an IoC container could give you more flexibility than this (e.g. inject shared objects etc).

  3. Testing is now more complex as you cannot mock out the "default" path (i.e. when $service == null) and so you would need a mix of Unit Tests (for the injected path, with a mocked dependency) and Integration Tests (for the defaulted path) to prove the correctness of your code.

  4. If your dependency itself has other dependencies, which also use constructor injection, the default construction path soon becomes unwieldy, and leads to even more coupling, as you now need to do all the hard work resolving dependencies that your IoC container would have done for you, e.g.

if(null === $service){
    $service = MyNewDefaultService(RepoFactory.Create(LoggerFactory.Create()), ...)
}

TL;DR Although this approach might be a useful during a transient stage while you are migrating from a coupled hierarchy to a loosely coupled Dependency Injected hierarchy managed by an IoC container, the true benefits of the Dependency Inversion Principle will only be fully realized once the constructors only have one path, viz by coupling on an abstraction to all dependencies, not to any concrete implementations.

Community
  • 1
  • 1
StuartLC
  • 104,537
  • 17
  • 209
  • 285
  • So are you suggesting, that if a class needs a service then inject the object and do not provide a default fallback class? My reasoning and why i do this a lot is to reduce the amount of upfront work creating objects and tying things together. My application is only a toy application for the moment trying to learn these concepts but for sure i want to get things done properly to learn – user1572427 Aug 24 '12 at 10:03
  • @user1572427 From a purist point of view, yes, you should leave the construction / lifespan up to the container. However, I've used your hybrid pattern before, e.g. when a customer has indicated that they 'are not yet using DI but may do so in the future', so the hybrid gives some future proofing. Instead of hardcoding the new, consider also using a classfactory. – StuartLC Aug 24 '12 at 10:08