Christian Neukirchen wrote: > Joel VanderWerf <vjoel / path.berkeley.edu> writes: > >> <i>Using the InjectableContainer module for "dynamic" or "fallback" >> injection, using <tt>method_missing</tt>:</i> >> >> require 'mindi' >> >> class Transformer >> def transform string >> string.gsub(pattern, &replacement) >> end >> end >> >> class TransformerContainer >> include MinDI::InjectableContainer >> >> pattern { /foo/ } >> replacement { proc {|match| match.upcase } } >> transformer { Transformer.new } >> transform { |str| transformer.transform(str) } >> end >> >> cont = TransformerContainer.new >> s1 = cont.transform("fo foo fee") >> s2 = cont.transform("fo foo fee") >> p s1 # ==> "fo FOO fee" >> p s1.equal?(s2) # ==> true >> >> >> Note that the Transformer class is written without explicitly linking up >> to services (either in initialize or in setters). It just assumes that >> the services will be defined in the container. > > So, if I get that right... the Transformer instance gets extended with > the TransformerContainer automatically? Or how does it know about > "pattern" and "replacement"? The Transformer instance is not extended with the TransformerContainer. It's extended with a simpler module that just has a #method_missing. More precisely... Here's what happens when the service is instantiated (i.e. the "{ Transformer.new }" block is called in response to the #transformer method on the container): The service object (the Transformer instance returned by the block) is given an instance variable, @__injectable__object__, whose value is set to the container. Also, the service object is extended, but only with a fairly minimal module called Injected: module Injected def method_missing(*args, &block) @__injectable__object__ || super @__injectable__object__.send(*args, &block) rescue NoInjectedMethodError super end end The NoInjectedMethodError is raised by the container's own method_missing. So the method search order for the service object is: 1. the service object itself (the Transformer instance) 2. other services in the container (this is how "replacement" and "pattern" are resolved) 3. method_missing, as defined in the ancestry of the service object > Isn't that a clear and obvious violation of encapsulation? Yes. Better to be clear and obvious about it ;) I'm not completely convinced by this style of DI. It may be no worse than others, though. In (say) setter injection DI there is a coupling between the container and the service, because the two need to agree on some list of accessors that the container will write to and the service will read from. In this proposed style of DI, the coupling is via references in the service to as-yet-undefined methods. It's sort of like defining a class that will have Enumerable mixed into it, but not providing any methods other than #each. Or like delegating and assuming that the target will define certain methods. > And won't it hide lots of bugs due to typos or doubly defined methods? If the container defines some method that is already defined in the service and which the service is not expecting the container to provide, the service will not be affected--it will continue to use its own method, as it should. The service and the container just need to agree (implicitly, at least) on the list of methods the service expects the container to provide. A typo in the service could invoke a container method (i.e., another service) by mistake. I suppose the Injected class could, instead of defining #method_missing, provide just a class method, called #uses or #uses_services, which lets you specify explicitly what services it uses (and defines a method for each one): class Transformer extend SomeModuleThatDefinesTheUsesServicesMethod uses_services :pattern, :replacement def transform string string.gsub(pattern, &replacement) end end (This approach would still need the @__injectable__object__ hackery.) But I'm not sure that typos are a serious enough danger to require this much explicitness. It would be that much harder to subclass Transformer and add mocked versions of pattern and replacement, for example, or to mix in a module that provided pattern and replacement in some other way. I'd prefer the service not to have to know it is going to be used in a container and only to know that certain methods will be defined somehow. "Duck services" anyone? -- vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407