In general setter injection is frowned upon. The main reason is mostly that an object should have all its dependencies at the creation time. That is very reasonable, but I find it a tad on the paranoid side. Or better put, I have been using setter injection only and have not found any problem ever. Furthermore, by having setter injection my objects and test code has remained cleaner than the alternative options.
Here are the rules
- Depend only on interfaces
- Don't use new
- Don't define constructors in the class
Depending always on interfaces sounds extremist. But it happens naturally if a system is developed outside-in mocking the dependencies. The principal advantage of mocking is increased focus at the time of writing the code, the decoupledness is a side effect. Another side effect is that the interface becomes the public interface of the class.
Therefore making things public in the class doesn't break encapsulation. I do confess that having more public and virtual elements in the class obscures the understanding of what the class offers. But the interface is right there and it's easy to see what the class is exposing to others thru it.
Depending only on interfaces needs to be accompanied by forbidding the use of new. If an object has a dependency say "Command ToSave;" and does "ToSave = new SaveCommand();" to create it. The class would have a dependency on both the interface Command and the class SaveCommand.
Once new is forbidden there are two ways to create a class, either by having a factory object or by using a DI framework. Both approaches would lead to the decision of setter injection vs constructor injection. I chose setter because the class ends up cleaner that way by not needing constructor logic.
There's no way of generating an object on an unstable state because the dependencies are either injected by a framework or they had to be passed as args to the factory method. There's a backdoor to the object via new but it is simply forbidden and conveniently available to the test code.
The test code becomes simpler because we need to mock only the dependencies involved on a particular behavior. It is a smell to have multiple dependencies and only some of them needed at a given time but it happens. Even if it's only at some stage before the object is refactored into a better design.