From: Brian Candler Date: 2004-10-07T19:10:17+09:00 Subject: Re: Ruby-esque Inversion of Control On Thu, Oct 07, 2004 at 03:05:22PM +0900, Jim Weirich wrote: > Charles O Nutter wrote: > >I forget who made this point at RubyConf, but the best example of why > >IoC and Copland are good things is the idea of a Black Box. > > I've added my attempt at explaining DI/IoC at http://onestepback.org. I > would be very interested in getting feedback on the effectiveness of the > article. I was completely unaware of DI/IoC until now, so hopefully I'm a good candidate :-) I'd say it's an excellent document. I immediately recognised the Service Locator pattern, which I've been using without knowing it as such. To build the webapp that you describe, rather than building the construction knowledge into Webapp.initialize, I would build an outer skeleton 'runner' which instantiates all the objects and links them together: #!/usr/local/bin/ruby require 'mywebapp' logger = Logger.new database = Database.new(:logger=>logger) error_handler = ErrorHandler.new(:logger=>logger) quotes = StockQuotes.new(:logger=>logger,:error_handler=>error_handler) authenticator = Authenticator.new( :logger=>logger,:error_handler=>error_handler,:databbase=>database ) webapp = Webapp.new( :logger=>logger,:error_handler=>error_handler,:databbase=>database, :quotes=>quotes,:authenticator=>authenticator ) webapp.run Clearly, these option hashes are similar to your Service Locators, but I'm not tied to using them; if a particular object requires (logger,database) as separate parameters, I just pass them in. I prefer hashes as it allows items to be optional, e.g. pass in a logger or not. In practice, there tends to be multiple versions of the skeleton. I might have one version which runs under fastcgi, and another which starts a webrick server, for example. Or I might decide to put the one of the objects on a different machine, and access it via DRB. So the main problem I have is that I end up with lots of very similar skeletons for different uses (unit testing, command-line runner, fastcgi, webrick etc), so if the plumbing needs to change, I have to remember to change each of the skeletons accordingly. DI would still require this plumbing, so I'm not sure it helps with this particular problem, except perhaps I could put some of the invariant parts of the DI skeleton into a library. You point out as a limitation of the SL approach: > Also, Suppose both StackQuotes and Datebase found their loggers using > :logger, but we want to give them separate logger instances for some > reason. The explicit dependence on the name of the logger service makes > this a bit difficult. I don't have this problem; I just write logger1 = Logger.new logger2 = Logger.new database = Database.new(:logger=>logger1) error_handler = ErrorHandler.new(:logger=>logger1) quotes = StockQuotes.new(:logger=>logger2,:error_handler=>error_handler) So the main advantages I can see for the DI approach are: 1. When object creation must be dynamic, i.e. you can't pre-plumb everything [I presume you could pass the DI object into the application, and let it create things on demand later]; 2. When you don't want to think about the creation order to satisfy dependencies. Is that a fair summary, or are there other advantages? Finally, one thing I don't understand. In your final example, using DI in your method "def create_application", I see all the dependencies registered but not actually something which say "create me an :app and run it". Is there something missing? Or does the DI framework simply try to build one of every registered object in the dependency tree? Regards, Brian.