From: Joel VanderWerf Date: 2004-11-14T13:43:50+09:00 Subject: Re: [ANN] Copland to Needle article on RubyGarden (LONG) Jamis Buck wrote: > Joel VanderWerf wrote: > >> Jamis Buck wrote: > > >>> reg.define do |b| >>> b.printer do |ctr,info| >>> logger = ctr[:logs].get( info ) >>> lambda { |kind| Printer.new( logger, kind ) } >>> end >>> >>> b.code_printer { |ctr,| ctr.printer.call( :monochrome ) } >>> b.image_printer { |ctr,| ctr.printer.call( :color ) } >>> end >>> >>> I've actually incorporated this approach into various places in the >>> upcoming new release of Net::SSH, and it works beautifully. >> >> >> >> That captures the parameterization, but it doesn't capture the >> multiton[1] service model of my code. A unique instance is generated >> for each distinct argument list (distinct in the sense of hash keys, >> which is to say #eql?). What's the most natural way to do that with >> Needle? > > > Actually, the example does--I just enforced the singleton (multiton?) > constraint in the code_printer and image_printer services. The +printer+ > service itself, though, will generate a new instance for each invocation. Right, I missed that. But that way makes it hard to ensure that there is only one printer of each type. There might be a log_printer service that also requests the monochrome printer. I guess you could define a #{kind}_printer service for each possible kind of printer, and implement, in terms of these services, a #{usage}_printer for each possible usage, including code, image, and log. In this case there are just a few kinds, but what if the printer arguments have more possible variation? What if, in addition to kind, there is an integer argument (a port number, say)? Btw, I'd now rewrite my printer snippet as: def printer_hash @printers ||= {} end private :printer_hash def printer(kind) printer_hash[kind] ||= Printer.new(kind) end both fixing a typo ( = where I meant ||= ) and using a separate service point, for the printer_hash, which can be declared private. It seems like private services would be awkward to implement in Needle, at least with the hash-based interface. (With the method sending interface, I guess you could just declare it private in the same way.) Thoughts? > However, you could also create a service factory. It would do just as > you did, with a Hash for caching created instances. > > class Printer > def self.get( name ) > @printers ||= Hash.new > @printers[ name ] ||= new( name ) > end > > private_class_method :new > def initialize( name ) > ... > end > end > > reg.define.printers { Printer } > > p1 = reg.printers.get( :monochrome ) > p2 = reg.printers.get( :monochrome ) > assert_same p1, p2 This feels like it breaks encapsulation: the Printer class shouldn't know about the service model that the container is using for it. In this case, the service model may be an unavoidable consequence of how the Printer class works, and so it should be enforced by the class itself. But there are classes that could be used with different service models in different coexisting containers: a general log class that, in one container, is threaded, but in another container is managed by a multiton service whose keys are log type, (e.g. :security, :information, :warnings, and :errors). > In fact, this is almost exactly how the log-factory service in Needle > works, although the LogFactory is a distinct class from the Logger. > > I'm still thinking through how to implement something like this in > Needle itself. It'll probably wind up being, as you suggested, a > pipeline element of some sort, although the parameterization issue goes > a bit deeper. I'm looking forward to watching Needle evolve.