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 ) }
endb.code_printer { |ctr,| ctr.printer.call( :monochrome ) }
b.image_printer { |ctr,| ctr.printer.call( :color ) }
endI'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 )
endprivate_class_method :new
def initialize( name )
...
end
endreg.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.