From: Jim Weirich Date: 2005-02-25T14:39:06+09:00 Subject: Re: surprising: class A; end; A === A ==> false On Thursday 24 February 2005 10:50 pm, Sam Roberts wrote: > > If (1), then remember that you can always open up any class and add more > > behavior. > > Isn't part, and *shouldn't* be part. Because the services are open ended ... got it. [... visitor pattern elided ...] > The reason you suggest this, [...] when you add a new input type you have > to find all the 'handling' code, and upgrade it. > > However, I don't have this problem, I'm actually using a visitor pattern > already, and I've encapsulated knowledge in what (I think) is the right > place. And the services only respond to the subset of the questions they know, so there is no problem with adding new questions and having to update all the services to know about the new question. Ok. Thanks for the code snippets. They really helped make the problem clear. I've got one more variation to toss in your direction. I don't know if this is any better than what you already have, but I found it to be interesting ... The basic twist is that it uses method names to represent the question (rather than the type of the question object) and the question object asks the service if it can answer the question. Here's the code ... # BEGIN ======================================== # Generic question class. Actual questions derive from this class and # define their specific question with the +consider+ command. class Question def self.consider(sym) define_method(:ask) do |service, answers| if service.respond_to? sym service.send(sym, answers) end end end end # Specific Questions I might need answered in the morning. class RainingQuestion < Question consider :is_raining end class CloudyQuestion < Question consider :is_cloudy end class JamQuestion < Question consider :traffic_report end class ClothingQuestion < Question consider :are_my_socks_matching end # Now some services that might (or might not) answer some # (or all) of my questions class WeatherService def is_raining(answers) answers << "It is raining." end def is_cloudy(answers) answers << "It is cloudy." end end class TrafficService def traffic_report(answers) answers << "No Traffic Jams." end end # Suppert functions and main loop def read_questions [RainingQuestion.new, JamQuestion.new, CloudyQuestion.new] end def send_answers(answers) puts answers end my_services = [WeatherService.new, TrafficService.new] questions = read_questions answers = [] questions.each do |question| my_services.each do |service| question.ask(service, answers) end end send_answers(answers) # END =============================== I'm not sure if I like using methods as negotiator between answers and questions. It works pretty well, but it does add an arbitrary piece of information into the mix. Hmmm ... another idea would to take the original code and use a hash map instead of a case statement. The services would lookup the question class in a hash map and execute any blocks found there. Well, thanks for indulging me. I've found this puzzle to be as enjoyable as the Weekly Ruby Quiz. -- -- Jim Weirich jim@weirichhouse.org http://onestepback.org ----------------------------------------------------------------- "Beware of bugs in the above code; I have only proved it correct, not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)