From: Trans Date: 2007-06-16T12:46:14+09:00 Subject: Re: Using extend for initialization settings? On Jun 15, 4:35 pm, Robert Klemme wrote: > > > Hmm... I think maybe my point is being missed. The goal is to > > initialize a class, not create a simple data struct. > > Actually your code is about initializing an instance - not a class. > Basically you just transform a Hash into some other data structure, a > module with constant accessors in your case. At the moment I fail to > see the benefit over something like this: > > class Hash > def init(x) > each do |var, val| > x.send("#{var}=", val) > end > end > end > > class Foo > def initialize(settings) > settings.init(self) > end > end > > I find the approach using a module somewhat convoluted. Also a module > simply does not seem the right vehicle IMHO. For example, what happens > to attr_accessors that you define in that class? Either they override > your anonymous module's methods or the other way round. This does not > seem a good solution to me. Attrsibutes would have precedence with how things work now. But that is an interesting point. Not that we have this in Ruby presently, but a #prepend instead of #extend could override attrs and would even allow pre-object AOP. But in any case, if one were using this type of constructor, one wouldn't be using attributes. > > I think the neat thing about this technique is that it could go beyond > > just assigning values, and provide a clean means of dependency > > injection. > > > class Module > > def to_module; self; end > > end > > > module Container > > def log(msg) > > puts msg > > end > > end > > > class Foo > > def initialize( settings ) > > extend settings.to_module > > end > > def report_ > > log("Ready.") > > end > > end > > > f = Foo.new(Container) > > > f.report > > > produces > > > Ready. > > Basically what you do is you require an instance that implements a > particular method that you introduce and which has to return a module > and you promise to extend the instance with that module returned. But > this means at the same time that there always *has* to be a module. I > am not sure whether that's a good idea because for one every module > comes at a cost and I think it's not the proper means to carry some > initialization info. Yea, I'm not really considering the cost at this point. Certainly this is not the kind of constructor needed for for every class. I'm thinking more along the lines of high-level integration classes -- integrating the various dependencies of an application, say. Your are right that this is an exploration in DI/IoC. > Also, Module#to_module and Hash#to_module server > two completely different purposes: the former helps extending an > instance with a predefined module (functionality) and the latter > provides key value pairs via a particular interface. That's true. But I think that polymorphism can be useful. Production code uses a functional module, but the class can be tested with a mock- up via a simple hash. > The more general solution for the "pass argument X that implements > method Y" is of course - blocks. So the pattern that you quoted > earlier is more flexible and general IMHO. I mean > > def initialize(&b) > instance_eval(&b) > end > > Combining that with Hash#init from above you can solve these tasks with > > f = Foo.new { {:foo=>"bar"}.init(self) } > f = Foo.new { extend Container } However, this is TOO flexible, because it offers no means of control over what's being passed in. It basically just opens the up the object to alteration 100% rather then just filling in "slot requirements". For example we could do: class Foo def initialize( di ) di = di.to_module raise "Unmet Dependencies" unless di.method_defined?(:x) extend di end end Maybe a wee bit of a more "realistic" example would help (it not a real one, but it's much closer to such). class Archiver def initialize( copyparams, language=nil, fileutils=nil ) extend copyparams.to_module extend language.to_module if language extend (fileutils ? fileutils.to_module : FileUtils) end # copyparms def to raise "where to?" end def from './*' # default value end # language def greeting "copying..." end # main def copy File.cp_r(from, to) end end So how about a dryrun in Spanish: module SpanishLanguage def greeting "copiado..." end end copyparms = {:from => '/home/trans/pics', :to=>'/backup'} archiver = Archiver.new( copyparms, SpanishLanguage, FileUtils::DryRun ) archiver.copy We can alter the interface: module EnglishLanguage def greeting "copying..." end def ask_from_where "from where?" end def ask_to_where "to where?" end end module CopyParams def to puts ask_from_where gets end def from puts ask_to_where gets end end archiver = Archiver.new( CopyParams, EnglishLanguage ) archiver.copy Note, I didn't test this code. So forgive any bugs. I'm sure you get the idea though. I'm not sure how great an idea all this really is. That's why I'm asking about it. But is certainly seems very flexible. T.