This post originated from an RSS feed registered with Ruby Buzz
by Christian Neukirchen.
Original Post: Mark IV Coffee Maker on Dissident
Feed Title: chris blogs: Ruby stuff
Feed URL: http://chneukirchen.org/blog/category/ruby.atom
Feed Description: a weblog by christian neukirchen - Ruby stuff
Jim Weirich uses an excellent example for introducing Dependency
Injection in his OSCON 2005
talk, the Mark IV Coffee
Maker.
I’m not going to introduce it here except for the API, if you did
not read his slides yet (or even attended?), you are better off if you do
that now.
Basically, you have three classes that need to know of each other:
PotSensor, Heater and Warmer. You want to instantiate a Warmer.
class PotSensor
def coffee_present?; ...; end
end
class Heater
def on; ...; end
def off; ...; end
end
class Warmer
def trigger
if pot_sensor.coffee_present?
heater.on
else
heater.off
end
end
end
Jim uses his own container, Dependency
Injection/Minimal
in the talk, and makes use of Constructor Injection. It looks like
this when using DIM (example sightly simplified for blogging
purposes):
If we wanted to use Constructor Injection too in Dissident, we could
go like this:
class MarkIVConfiguration < Dissident::Container
def pot_sensor_io_port; 0x08F0; end
def warmer_heater_io_port; 0x08F1; end
def pot_sensor
PotSensor.new pot_sensor_io_port
end
def warmer_heater
Heater.new warmer_heater_io_port
end
def warmer
Warmer.new(container.pot_sensor, container.warmer_heater)
end
end
(Assuming container is self, this is even valid and working Ruby
code without using Dissident.) At least you would write it that way
some iterations ago. Recently, Dissident learnt of some useful
constructs, however, that simplify above greatly. We now can write:
class MarkIVConfiguration < Dissident::Container
constant :pot_sensor_io_port, 0x08F0
constant :warmer_heater_io_port, 0x08F1
provide :pot_sensor, PotSensor, :pot_sensor_io_port
provide :warmer_heater, Heater, :warmer_heater_io_port
provide :warmer, Warmer, :pot_sensor, :warmer_heater
end
I don’t think Constructor Injection can be more straight-forward in a
language that doesn’t allow for argument name (or “type”) reflection.
We instantiate the warmer by setting up the container and calling:
Dissident.with MarkIVConfiguration do |c|
c.warmer
end
Now, let’s do the same with Setter Injection in Dissident.
class PotSensor
inject :pot_sensor_io_port
end
class Warmer
inject :pot_sensor
inject :warmer_heater
end
class MarkIVConfiguration < Dissident::Container
constant :pot_sensor_io_port, 0x08F0
constant :warmer_heater_io_port, 0x08F1
provide :pot_sensor, PotSensor
provide :warmer_heater, Heater, :warmer_heater_io_port
end
Note how Setter and Constructor Injection can be mixed. (PotSensor uses
Setter Injection, Heater gets its port via Constructor Injection.)
Now, watch how we instantiate that:
Dissident.with MarkIVConfiguration do
Warmer.new
end
As you can see, this looks like “ordinary” Ruby, as if DI was not used
at all. It may not make a big difference if you see the whole code at
once, but in my experience distributing the injections to the
classes that need it greatly simplifies coding because things are
where they are related to.
Also, see how easy we can mock, say, for testing:
class MarkIVTestConfiguration < MarkIVConfiguration
provide :pot_sensor, MockPotSensor
provide :warmer_heater, MockWarmerHeater
end
Dissident.with MarkIVTestConfiguration do
Warmer.new
end
Dissident is now in a state where I think I can make the general
public have a look at the code, so you are welcome to get it at the
darcs repository (or just browse the code there).
I hope to make a proper release really soon now.