Why I prefer constructor-based depency injection
Listen to this article
On the current project I'm on, and no doubt on many of yours, we have restrictions on the number of parameters you may declare for a method. This, hopefully, forces developers to re-evaluate what they are passing around. For example, use a DateRange instead of dateFrom and dateTo.
Unfortunately there is a simple way to subvert that process by simply declaring everything as "setters" which typically have only one parameter. Then all the lovely detail becomes much harder to see as it's hidden in the morass of the aptly name mutators (yet another reason I dislike getters and setters).
Declaring service dependencies in constructors allows me to see them all in one go. It immediately becomes apparent if a class depends on "too many". Something that is much harder to see when you use setters. It also allows us to construct objects in a valid state with all the obvious benefits.
Another advantage to using a constructor is that only the creator need know about the dependencies. In our case it's our ServiceRegistry. Once an object has been constructed (either by the registry configuration for singleton services defined by interfaces or by calling ServiceRegistry.newInstance(Class) allowing the construction of any class that depends on a service) client code is unaware of the dependency.
I've recently been converting large swathes of code on this project from static lookups and setter-based dependency injection (DI) to constructor based and in the process finding stuff that was not previously apparent. These include classes that depend on too-many services, classes that depend on services they just shouldn't and classes that are quite fragile because callers didn't realise they had to call the setters in a particular order! None of these was in anyway obvious previously.
The down-side of course is that constructor parameters make inheritence a pain. If you have a deep and/or wide class heirarchy, you will need to declare the dependencies in the constructors of all the sub-classes. Not only is this tedious but it necessarily exposes the dependency to classes that probably don't directly make use of the service.
My counter to this is simple: don't have deep and/or wide class heirarchies. I rarely find the need for them and they're usually a code smell. The fact that we can extend classes to inherit behaviour doesn't mean we should. No doubt a topic for another rant :-)
As always there are exceptions. Sometimes you just can't get around the need for setter-based DI, usually when you are constrained by someone elses API. Most noteably in our case is the fact that (for reasons I don't want to go into urgh!) we're forced to use versions of third-party libraries (Struts) that want to directly construct our classes with no factory mechanism. This means we have Actions that must have a default constructor.
You may have noticed a distinct lack of the 'T' word. I deliberately stayed away from describing how all this makes classes more testable and is a natural consequence of doing TDD anyway because if you're doing TDD you already know this and if you don't like TDD (really?) I didn't want to put you off the idea by implying this was all about testing, which it's not.
Comments
I completely agree. I find 'smart' constructor injection not only establishes and protected collaborator invariants, it makes it easy to see what collaborators _are required_ for the class to work. If you have too many, yes, perhaps it's time to refactor. I think setter injection is appropriate for optional properties -- the only other real advantage with setters (besides the capability to configure existing out-of-your-control javabeans with no change) is with IoC containers like Spring, setters makes the action of configuring a property more explicit (i.e property name="foo" is more specific than constructor-arg index="0").
Posted by: kdonald | April 17, 2004 12:22 PM
FWIW, our "ServiceRegistry" is very simple (maybe 60 lines of code?) but is based purely on types (interfaces). That way the constructor argument order is not important.
Also, the registry only ever hands out proxies allows us to dynamically change the implementation at runtime without needing to reconstruct or re-configure dependants. It also means client code can't try and imply anything about the services behaviour by using instanceof, etc. Even thinking about doing such a thing (casting) seems daft I know but believe me, if a developer CAN do something, at somepoint they WILL LOL.
I'm pretty sure Nano, Pico, etc. behave or at least can behave in a similar way. Someone on our team is already looking at possibly replacing our custom implementation with one or other of the open source IoC containers but right now we have everything we need so it's a low priority.
Posted by: Simon Harris | April 17, 2004 12:38 PM
I pretty much completely agree. Don't provide any ability for required references to be broken, period. Type 2 IoC introduces a non-obvious dependency on the container and the particular configuration.
Posted by: Brian McCallister | April 17, 2004 12:41 PM
Agreed. Constructor-based DI is really a part of the larger problems of constructing objects in a valid state and immutability. Definitely a topic for another rant :-)
Posted by: Simon Harris | April 17, 2004 12:48 PM
I agree that shallow inheritance is the way to go ... something I've personally outgrown is multi-level inheritance hierarchies.
Using HiveMind (or other IoC container/microkernel), much freer to build using aggregation/delegation.
Interfaces are key to HiveMind, and where we diverge. HiveMind does support constructor injection, but I prefer property injection. I find it easier to test.
Because HiveMind is explicitly a service interface and a POJO implementing the interface, the POJO has the settable property, not the interface. I find this defuses the arguments about setters being "dangerous".
In fact, after a HiveMind core service implementation is instantiated and configured it is almost always hidden behind a proxy: another object implementing the interface that does *not* have any settable properties (but delegates the service interface methods to the real implementation). This is part of HiveMind's just-in-time service creation (core srevice implementations are created when the first method on the service is invoked, not before).
There is explicitly no way to work through the proxy to get to the core service implementation.
This is like an extra layer of armor that keeps client code from accessing the properties of the core service impl incorrectly.
Posted by: Howard M. Lewis Ship | April 17, 2004 12:54 PM
Got to agree with you on this one, Simon. One of my complaints about Struts, for example, is the mess that the typical Struts form ends up as. I'd really like to see constructors used to populate beans (at least those in request scope) instead of setters.
Posted by: Robert Watkins | April 17, 2004 01:11 PM
There's nothing wrong with multi-level inheritance hierarchies, _within_ one framework or lib. If you look at Spring Framework for example, it leverages inheritance to pretty good effect.
For some third party to come along and try to subclass code they nothing about the internals of, is a fundamentally different propositon, and I think can succeed only when the extention points and usage semantics are carefully thought out and documented...
I think settters and getters have their place. Once you get past a few constructor arguments constructos get ridiculous. But most of these setters and getters should not be in the interface which everybody except the creator of the object in question should be using to access the object.
Posted by: Colin Sampaleanu | April 17, 2004 01:38 PM
Sure, I'm not suggesting don't use inheritence but often it's a convenient way to implement "code buckets". Like most things, there are no hard and fast rules just observations.
As for large numbers of constructor parameters being a problem, that was the whole point. I'm arguing that too many parameters is in fact a problem but that the solution is not to just go and dump them into setters. As I mentioned, simply adding setters often simply hides those dependencies and the classes just continue to grow and do stuff they probably shouldn't be responsible for.
Instead, maybe we need to look at why the class depends on so many things and factor those out into other classes.
Posted by: Simon Harris | April 17, 2004 01:47 PM
Simon,
My reasons for using CBDI are very similar:
- The dependencies for any class are in ya face!
- Easy to see that any given class is possibly doing too much work, i.e., too many depenendencies
- I find TDD naturally leads towards this approach
- because of point 2, i find that my classes tend to become smaller and hence have well defined roles and responsibilities.
- static lookups totally suck!
Cheers,
Damian
Posted by: Damian Guy | April 18, 2004 06:36 AM
Maybe we should be able to choose between constructor- or setter-based dependency injection?
There's no such thing as 'the right thing' in design, everything is some kind of trade-off.
Offering support for both options, in my opinion, is the way to go.
Posted by: tetsuo | April 19, 2004 11:56 PM
I was very careful to use the term 'prefer' in the title for this very reason :-)
Posted by: Simon Harris | April 20, 2004 12:03 AM
> Offering support for both options, in my opinion, is the way to go.
Well, that's exactly what the Spring Framework already does: offering support for both constructor and setter injection, allowing to mix and match as requirements and tastes demand. I haven't looked at HiveMind in detail, but I take Howard's word for it that both are supported in a sophisticated fashion too.
However, in general, double-check such claims: While Pico does support setter injection, it does so in a crude fashion: For example, it doesn't support optional properties, just satisfying all at once (as of very recently). IMO, the very point of setter-based injection is that it allows for defaults and also convenient type conversion.
Posted by: Juergen Hoeller | April 20, 2004 12:20 AM
I agree that setter based injection is not good.
Because, I think that Object immutability is very important.
So, I like to declare property as 'final'.
If we want to declare as 'final', in Java, we must use constructor based injection.
but, constructor based injection is not suitable for inheritance.
With such a reason, I use 'mixed type' injection (I named it "type 2.5").
example is here.
abstract class Service{
//we can use setter for dependency injection
//(advantage of "type 2")
static class ServiceInitParamsBean{
private String serviceName;
private long lifeSpan=1000*60*15;
public void setServiceName(String serviceName){
this.serviceName=serviceName;
}
public String getServiceName(){
return serviceName;
}
public void setLifeSpan(long lifeSpan){
this.lifeSpan=lifeSpan;
}
public long getLifeSpan(){
return lifeSpan;
}
}
//------------------------------------------------------
//In "type 2.5" injection,
//we can declare depencency parameters as 'final'
//(advantage of "type 3").
private final String serviceName;
private final long lifeSpan;
//"type 2.5" injection
Service(ServiceInitParamsBean initParamBean){
serviceName=initParamBean.getServiceName();
lifeSpan=initParamBean.getLifeSpan();
}
//standard "type 3" injection
Service(String serviceName,long lifeSpan){
this.serviceName=serviceName;
this.lifeSpan=lifeSpan;
}
public abstract void invoke();
}
class RealService1 extends Service{
static class RealService1InitParamsBean
extends ServiceInitParamsBean{
private int maxConnections=10;
public void setMaxConnections(int maxConnections){
this.maxConnections=maxConnections;
}
public int getMaxConnections(){
return maxConnections;
}
}
//------------------------------------------------------
private final int maxConnections;
//------------------------------------------------------
//"type 2.5" injection
//in "type 2.5" injection,
//we only need to write one constructor
public RealService1(RealService1InitParamsBean initParamBean){
super(initParamBean);
this.maxConnections=initParamBean.getMaxConnections();
}
//------------------------------------------------------
//"type 3" injection
//default constructor for "type 3" injection
public RealService1(String serviceName,long lifeSpan,int maxConnections){
super(serviceName,lifeSpan);
this.maxConnections=maxConnections;
}
//using default value for 'lifeSpan'
public RealService1(String serviceName,int maxConnections){
this(serviceName,Long.MAX_VALUE,maxConnections);
}
//using default value for 'serviceName'
public RealService1(long lifeSpan,int maxConnections){
this("anonymous",lifeSpan,maxConnections);
}
//------------------------------------------------------
public void invoke(){
//implementation of RealService1
}
}
class RealService1Caller{
public void callService(){
RealService1.RealService1InitParamsBean initParams=
new RealService1.RealService1InitParamsBean();
initParams.setServiceName("foo");
initParams.setLifeSpan(1000*60*60*24);
initParams.setMaxConnections(100);
Service s=new RealService1(initParams);
s.invoke();
}
}
Posted by: takahashikzn | April 21, 2004 10:05 AM
Type 2.5 - just goes to show that there is a lot of DI discussions going on everywhere. I agree with that Ctor based DI is really clean, good etc - specifically while testing. But there are issues no doubt, one being the problem of domain modeling - the provervial car and traffic light issue - does the car take in a traffic light because it needs it to decide about its movements. Moveover, I have come across senarios where I wanted to move in multiple information( not services) into my class. which meant that I have tons of constructor arguments. I think thats when your 2.5 ;) DI comes in . This can be moved in , in a struct. But I still think this is again just type 3 rather than being anything else. Your DTOs are just not components and you don't have to bother about them being intialized properly. Rather its when they are used by a component that you need to bother about initialization - of the components.
Posted by: Pdeka | April 23, 2004 10:51 PM
Consider the Boy-Girl example of Aslak Hellesoy in Picocontainer
Let us assume that the in addition to the Boy class, the Frog class also implements Kissable.
Could you explain how to inject a Frog instance into the Girl class at runtime using constructor injection when the Girl class accepts a Kissable object in its constructor.
In Spring using setter injection, this is configured in an XML file.
Posted by: Simon John | June 2, 2004 05:04 PM
I'm not familiar with the example you mention but I can imagine the problem.
If it makes sense that a Girl only ever Kiss a single instance of a Boy OR a frog, then constructing a Girl with a pre-defined Kissable makes perfect sense in which case configuration via an XML file or even code is entirely possible.
If that doesn't make sense and a Girl has a kiss(Kissable) method then I'd say it's not an IoC problem.
There are very few problems __that I have ever seen in the wild__ where a setter-based approach works when a constrcutor-based one doesn't.
At any rate I'll go and look at Aslaks example :-)
Cheers,
Simon
Posted by: Simon Harris | June 2, 2004 06:33 PM
If your question is related to exactly HOW to do constructor-based injection using configuration files etc., it's pretty simple to do using relfection (and in our case dynamic proxies).
Posted by: Simon Harris | June 2, 2004 06:45 PM
My question precisely was, if both instances of Kissable (say the Frog and Boy) was configured in the container, how would the container know which of them to initialize within the Girl class.
As you said, it uses reflection to resolve that.
Thanks Simon.
Posted by: Simon John | June 5, 2004 03:59 PM