Chad said:
For those not subscribed to RubyGarden's rss feed[1], Jamis Buck has
written a new feature piece discussing his journey from
dependency-injection-novice to two-time-IoC-frameworker. He also
discusses the (unusual) decision to start over on a framework that is
less than a year old and demonstrates the differences between these
two frameworks (Copland and Needle). Big thanks go to Jamis for his
fascinating contribution.
The article is available on RubyGarden's front page or at:
http://rubygarden.org/index.cgi/Libraries/copland-to-needle.rdoc
Request: If you'd like to write an article or tutorial or conduct an
interview to be published on RubyGarden.org, please send me an email
directly. I'm working to establish a rhythm of new content releases,
so if you're interested in contributing I would love to hear from you.
Thanks,
Chad Fowler
[1]
http://rubygarden.org/index.cgi/index.rss
Jamis Buck's and Jim Weirich's articles are great. I could never get
through the Java-oriented discussion of this subject, and now that the
subject is presented for rubyists a dim bulb is finally going on in my
head.
What really helps me think about this is to reduce it to some simple,
standard Ruby idioms, which don't provide as many features as Needle,
but are close enough to code that I've actually written to make it all
seem meaningful.
Let's take Jim's example[1], which is clear, rubified, and explicit
about DI constructs, and translate it into some concrete,
mundane ruby code without those constructs. Here's the original:
def create_application
container = DI::Container.new
container.register

logfilename) { "logfile.log" }
container.register

db_user) { "jim" }
container.register

db_password) { "secret" }
container.register

dbi_string) { "DBI

g:example_data" }
container.register

app) { |c|
app = WebApp.new(c.quotes, c.authenticator, c.database)
app.logger = c.logger
app.set_error_handler c.error_handler
app
}
container.register

quotes) { |c|
StockQuotes.new(c.error_handler, c.logger)
}
container.register

authenticator) { |c|
Authenticator.new(c.database, c.logger, c.error_handler)
}
container.register

database) { |c|
DBI.connect(c.dbi_string, c.db_user, c.db_password)
}
container.register

logger) { |c| Logger.new(c.logfilename) }
container.register

error_handler) { |c|
errh = ErrorHandler.new
errh.logger = c.logger
errh
}
end
And here's a rewrite that functions in about the same way, but without
being explicit about containers and services.
class Application
def logfilename
@logfilename ||= "logfile.log"
end
def db_user
@db_user ||= "jim"
end
def db_password
@db_password ||= "secret"
end
def dbi_string
@dbi_string ||= "DBI

g:example_data"
end
def app
@app ||= WebApp.new(quotes, authenticator, database)
end
def quotes
@quotes ||= StockQuotes.new(error_handler, logger)
end
def authenticator
@authenticator ||=
Authenticator.new(database, logger, error_handler)
end
def database
@database ||= DBI.connect(dbi_string, db_user, db_password)
end
def logger
@logger ||= Logger.new(logfilename)
end
def error_handler
@errh ||= (
ErrorHandler.new
@errh.logger = logger
@errh
)
end
end
def create_application
Application.new
end
So we're using the Application class itself as the container, and
we're using the instance methods of this class as the service points.
The methods return the services, and also store them in instance
variables. A service is registered using "def". Instead of using the
block parameter to pass the container to the service definitions, you
just use self. The familiar ||= idiom provides the singleton service
model.
It's quick and dirty, and it obscures the conceptual structure, but if
you're familiar with ruby, you can see immediately what is going on.
You've probably even written code a little like this. Why would you
want to "upgrade" your code to a more explicit form of DI? There are
several disadvantages to this implicit DI style, aside from the
implicitness itself:
- It doesn't help much with things like interceptors, although you
could bring in your favorite AOP library.
- It mixes your service namespace with the namespace inherited from
Object and Kernel: maybe you want a service called "dup" or "puts",
but then you cannot call the Object and Kernel implementation of
these methods from within methods of the Application class.
- Reflection must be done using standard ruby reflection on classes,
so you have to, for example, filter out methods that are not really
services.
OTOH, you can use class inheritance and module inclusion to build
trees of container definitions in a very natural and familiar way,
emulating some of the functionality of Needle:
module LoggingServices
def logger; ...; end
end
module GUIServices
end
module DatabaseServices
end
class MyServiceContainer
include LoggingServices
include GUIServices
include DatabaseServices
def app
MyApp.new(logger, gui, database)
end
end
The other service models besides the singleton model are also easy to
implement with quick and dirty ruby code:
- Threaded:
def per_thread_logger
@per_thread_logger ||= {}
@per_thread_logger[Thread.current] ||= Logger.new
end
- Prototype:
def gui_button
MyButtonClass.new
end
- Deferred (ok, this one gets a little messy, and the details should
be abstracted away by some fancy metaprogramming):
def big_resource
@big_resource || big_resource_proxy
end
def big_resource_proxy
@big_resource_proxy ||= (
proxy = [proc {|br| @big_resource = br}]
def proxy.method_missing(*args, &block)
big_resource = BigResource.new
at(0).call(big_resource)
big_resource.send(*args, &block)
end
proxy
)
end
private :big_resource_proxy
There are other service models that are very easy to construct with
methods, but I'm not aware of an equivalent construct in Needle:
def printer(kind)
@printers ||= {}
@printers[kind] = Printer.new(kind)
end
def printer_for_code_files
@printer_for_code_files ||= printer

monochrome)
end
def printer_for_images
@printer_for_images ||= printer

color)
end
It might be possible to do the above in Needle using Pipelines.[2]
Namespaces, if I understand the Needle docs correctly, work something
like this:
class PictureApp
class ColorNamespace
def red; PrimaryColor

red); end
def green; PrimaryColor

green); end
def yellow; @yellow ||= red+green; end
end
def colors
@colors ||= ColorNamespace.new
end
def picture
@picture ||= Picture.new

background => colors.yellow)
end
end
Alternately, if you want color instances to shared by all PictureApps,
you might want to define the colors service like this:
def colors
@@colors ||= ColorNamespace.new
end
I hope there aren't too many inaccuracies in the above, and that this
helps other folks move from older ruby idioms to the new idioms
that Needle gives us.
--
[1]
http://onestepback.org/index.cgi/Tech/Ruby/DependencyInjectionInRuby.rdoc
[2] Anybody know? Or are parameterized services a misuse of the DI
pattern?