This pattern will work (in fact, the (anti)pattern has a name - Bastard Injection), but there are issues relating to this approach:
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.
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).
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.
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.