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.