Constructor vs. Setter Injection
I think there are some good arguments for constructor injection in terms of a well-defined contract for what a particular object requires. However I believe it is impossible (?) to resolve circular dependencies using constructor injection and as I found out the other day, all of the applications I build have at least the potential for circular dependencies. Why then bother to go to the added complexity of supporting constructor injection if you’re not going to use it all of the time? After all, what could be worse than documentation that appeared to be authoritative but that was actually not complete?!
For now LightWire is setter injection only using a single base AddObject() method. There are lots of reasons not to go this way if you want your code to be self documenting, but if you see as documentation as a separate output from a core metadata repository rather than something you deduce from your cfc’s then I’m not seeing much in terms of a downside.



Constructor injection has other benefits, like potentially more testable code as well... because the CFC is "ready to go" at instantiation (because you required your dependencies to be injected via the "constructor") you know that once it's instantiated it's usable without having to call any other setters.
I know there's some debate, but I personally prefer to use constructor injection... if for no other reason that having to code up createObject().init() and then object.setFoo(foo) and object.setBar(bar) seems to be a waste of code. Then again, I'm finnicky that way. ;)
As for solving the circular reference issues with constructor injection, there are obviously times when property injection is going to be more useful... or you can make your constructor arguments optional on certain types of objects so you can instantiate one without the dependency and solve the references issue after the fact... which then circles back to the idea of using property injectors.
I haven't run into this issue yet because I don't really have anything that uses circular references yet... maybe someday I'll need to, but not at the moment.
Laterz,
J
Firstly I should clarify how I do setter injection (yeah, I know, if I'd published the code already you'd KNOW this!!!).
When you ask LightWire for an object, if it is a singleton, it looks to see if it has already been created (if it is a transient, it skips this step). If so, it just gives you the object. If not, it runs a routine that resolves all dependencies n-levels down (all of the dependencies of the dependencies of the dependencies - unlimited depth) while handling circular dependencies without getting stuck in a loop. It then creates ALL of the objects you need (the objects that your objects need, the objects that those objects need, and so on). It then creates the object you asked for. It then setter injects all of the dependencies into all of the objects you need and into the object you requested. It then gives you the object you asked for.
So, in practice while I am using setter injection, there is a single method call that does everything and the object you get back is fully loaded with dependencies, it is just that the problem is easier to solve this way and it includes the ability to handle circular dependencies which are actually pretty common.
Constructor injection lets you take existing code that knows nothing about dependency injection or ColdSpring or anything and have ColdSpring (or Lightwire?) use it as-is with no code changes.
Just to reiterate, I have existing code that works like this:
myService = createObject("component","path.to.my.Service").init(some,list,of,args);
At any point, I can take Service.cfc and have it managed by ColdSpring - with no changes to Service.cfc - and reuse it in another piece of code. And we can easily use ColdSpring in our original application too:
myService = application.cs.getBean("myService");
or even just:
function setMyService(svc) { variables.myService = arguments.svc; }
and have CS inject it.
No change to Service.cfc. It doesn't know. It doesn't care.
If you don't have constructor injection, you have to change any CFCs that expect init() to be called immediately on creation.
Yep, this is why I quite like constructor injection and that is what LightWire does now. You just need to have a bunch of cfarguments in your init() within service and (as you mentioned) you can take a cfc and move it in or out of a DI framework and it doesn't break the cfc either way.
The only point I'd make is that because (thankfully) there aren't constructors in CF (only the init() CONVENTION) you can actually resolve circular constructor dependencies as I do in LightWire which would be impossible in Java.
For example, if you're lazy loading, you just create first object without calling init and then create all the objects it depends on n-levels down without calling their inits. Then you call the init methods on all of them so it is some kind of mix of constructor and setter injeciton in that you get the benefit of resolving circular dependencies (which is impossible in Java constructor injection) but you still have all of the arguments in the init() method so you could remove your DI framework and replace it with manual passing of the dependent objects without making any change to the cfcs you're creating. Best of both worlds :->
This is also why I'm praying CF doesn't get constructors as if people started using them we'd have to genuinely choose between the reusability and documentation benefits of constructor injection and the fact that you can't resolve circular dependencies (which are quite common) without using setter injection.
Obviously some potential thread safety issues I'll need to think through as atomic NEQ instantaneous!
How can you call the init() methods in the correct order? Constructor injection means that there is a dependency chain between constructors (init() methods).
I want to get ProductService. ProductService depends on ProductDAO and UserService. UserService depends on ProductService and UserDAO. ProductDAO and UserDAO have no dependencies and we're lazy loading.
I create ProductService (without calling init, so it is unitialized - tacky, but bear with me). I then create all other dependent objects, n-levels down (ProductDAO, UserService and UserDAO) - again without calling their inits.
I then call their inits in any order. For instance, I call ProductService.Init() and give it the reference to the uninitialized UserService and ProductDAO. I then call ProductDAO.init() to initialize it (so now the ProductDAO referenced by ProductService is initialized). I then call UserService.init() passing it a reference to the initialized ProductService and the uninitialized UserDAO. I then call UserDAO.init(), initializing the DAO that was referenced by the UserService.
I do all of this as a "single operation", returning ProductService which is now initialized and fully loaded with initialized objects. Of course, while LightWire was doing all of this I passed in references to objects that hadn't been initialized, but as I wasn't asking those objects to do anything (I was just providing references), who cares?! By the time I returned the ProductService, it and all of its dependent objects were fully initialized, so I get the benefit of resolving circular dependencies AND using "constructor" based injection (clearly it ISN'T constructor based as I'm not using the inits as true constructors - but it LOOKS like constructor based init).
Make sense?
Two objects... each dependent on the other. Each is supposed to be an argument to the other.
CF cares about the type, not whether it's been completely "instantiated" or not... so if you create the object without calling init(), you can pass it into the second object's init() method. Then you can call init() on the first object, passing the second object into it. Since CFCs are passed around by reference, both objects will then be subsequently completely init()'ed.
It's an interesting idea... I can't really see any reason why it wouldn't work.
Also, I can't believe I didn't think of it sooner. It's so obvious, there's GOT to be a reason it won't work... ;)
J
Who knows, maybe one of my dumb questions will have ended up with an interesting answer?!!!
Make sense?
Just to get an idea, how often do you find yourself calling methods on an injected object as part of your init methods? Is this a theoretical, a seldom but essential, a seldom but could work around or an everyday occurance?
*sigh*
So much for simple solutions. :) Now that I think of it, I've had this conversation before...
If it really were that simple, it wouldn't have been so hard for the CS folks to take care of the circular references issues they had to deal with.
On reflection I'm just going to "YAGNI" this for now and leave the engine as it is - just documenting the limitation. I haven't yet come across a use case where I need to call a method on one of the dependent objects within the init method of another.
When/if I do, I'll just have to go back to requiring setter injection for circular dependencies and will have to spend an afternoon getting my head around the constructor dependency resolution order. If nothing else it'll be a cool algorithm to write!
Thanks for ALL the help guys - much appreciated. Between you, you'll make a programmer out of me one of these days :->
In the end resolving circular dependencies was fairly trivial and all code including dependency resolution and handling comes in at under 300 lines (which is consistent with what I heard someone else suggest a DI engine should require in a dynamically typed language - I want to say it was Bruce Eckel or Martin Fowler, but can't remember for sure).
Still need to add support for reading CS config files, basic AOP, and there are a bunch of Spring 2.0 features I'm also looking to add. For anyone interested, code is at lightwire.riaforge.org.