Constructor Injection vs. Setter Injection
by Miško HeveryThere seems to be two camps in dependency-injection: (1) The constructor-injection camp and (2) the setter-injection camp. Historically the setter-injection camp come from spring, whereas constructor-injection camp are from pico-container and GUICE. But lets leave the history behind and explore the differences in the strategies.
Setter-Injection
The basic-ideas is that you have a no argument-constructor which creates the object with "reasonable-defaults" . The user of the object can then call setters on the object to override the collaborators of the object in order to wire the object graph together or to replace the key collaborators with test-doubles.
Constructor-Injection
The basic idea with constructor-injection is that the object has no defaults and instead you have a single constructor where all of the collaborators and values need to be supplied before you can instantiate the object.
At first it may seem that setter injection is preferred since you have no argument constructors which will make it easy for you to create the object in production and test. However, there is one non-obvious benefit with constructor injection, which in my opinion makes it a winner. Constructor-injection enforces the order of initialization and prevents circular dependencies. With setter-injection it is not clear in which order things need to be instantiated and when the wiring is done. In a typical application there may be hundreds of collaborators with at least as many setter calls to wire them together. It is easy to miss a few setter calls when wiring the application together. On the other hand constructor-injection automatically enforces the order and completeness of the instantiated. Furthermore, when the last object is instantiated the wiring phase of your application is completed. This further allows me to set the collaborators as final which makes the code easier to comprehend if you know a given field will not change during the lifetime of the application.
Let's look at an example as to how we would instantiate a CreditCardProcessor.
CreditCardProcessor processor = new CreditCardProcessor();
Great I have instantiated CreditCardProcessor, but is that enough? No, I somehow need to know to call, setOfflineQueue(). This information is not necessarily obvious.
OfflineQueue queue = new OfflineQueue(); CreditCardProcessor processor = new CreditCardProcessor(); processor.setOfflineQueue(queue);
Ok I have instantiated the OfflineQueue and remember to set the queue as a collaborator of the processor, but am I done? No, you need to set the database to both the queue and the processor.
Database db = new Database(); OfflineQueue queue = new OfflineQueue(); queue.setDatabase(db); CreditCardProcessor processor = new CreditCardProcessor(); processor.setOfflineQueue(queue); processor.setDatabase(db);
But wait, you are not done you need to set the Username, password and the URL on the database.
Database db = new Database(); db.setUsername("username"); db.setPassword("password"); db.setUrl("jdbc:...."); OfflineQueue queue = new OfflineQueue(); queue.setDatabase(db); CreditCardProcessor processor = new CreditCardProcessor(); processor.setOfflineQueue(queue); processor.setDatabase(db);
Ok, am I done now? I think so, but how do I know for sure? I know a framework will take care of it, but what if I am in a language where there is no framework, then what?
Ok, now let's see how much easier this will be in the constructor-injection. Lets instantiate CreditCardPrecossor.
CreditCardProcessor processor = new CreditCardProcessor(?queue?, ?db?);
Notice we are not done yet since CreditCardProcessor needs a queue and a database, so lets make those.
Database db = new Database("username", "password", "jdbc:...."); OfflineQueue queue = new OfflineQueue(db); CreditCardProcessor processor = new CreditCardProcessor(queue, db);
Ok, every constructor parameter is accounted for, therefore we are done. No framework needed, to tell us that we are done. As an added bonus the code will not even compile if all of the constructor arguments are not satisfied. It is also not possible to instantiate things in the wrong order. You must instantiate Database before the OfflineQueue, since otherwise you could not make the compiler happy. I personally find the constructor-injection much easier to use and the code is much easier to read and understand.
Recently, I was building a Flex application and using the Model-View-Controller. Flex XML markup requires that components must have no argument constructors, therefore I was left with setter-injection as the only way to do dependency injection. After several views I was having hard time to keep all of the pieces wired together properly, I was constantly forgetting to wire things together. This made the debugging hard since the application appeared to be wired together (as there are reasonable defaults for your collaborators) but the collaborators were of wrong instances and therefor the application was not behaving just right. To solve the issue, I was forced to abandon the Flex XML as a way to instantiate the application so that I can start using the constructor-injection and these issues went away.
No comments:
Post a Comment