Ruby - Switching dynamically between protocols (with modules)
I would like to allow a person object (instanced from a Person class) to speak a language (which is a collection of public methods stored in Language module):
class Person
attr_accessor :current_language
def quit
# Unselect the current language, if any:
@current_language = nil
end
end
Suppose that languages are the following:
module Language
module Japanese
def konnichiwa
"こんにちは! (from #{@current_language} instance variable)"
end
def sayounara
"さようなら。"
end
end
module French
def bonjour
开发者_开发技巧 "Bonjour ! (from #{@current_language} instance variable)"
end
def au_revoir
"Au revoir."
end
end
module English
def hello
"Hello! (from #{@current_language} instance variable)"
end
def bye
"Bye."
end
end
end
Example of use:
person = Person.new
person.current_language # => nil
person.hello # => may raise a nice no method error
person.current_language = :english
person.hello # => "Hello! (from english instance variable)"
person.bonjour # => may also raise a no method error
person.quit
person.current_language = :french
person.bonjour # => "Bonjour ! (from french instance variable)"
As you can see, a language is such as a protocol. So a person can switch on a specific protocol, but only one at a time.
For modular reasons, storing each language into a module is friendly. So I think this way is the more logical Ruby way, isn't it.
But, I believe that it is not possible to write something like this:
class Person
include "Language::#{@current_language}" unless @current_language.nil?
end
According to you, what should be the best practice to do so?
Any comments and messages are welcome. Thank you.
Regards
You can do this pretty elegantly in Ruby if you arrange your modules correctly.
module LanguageProxy
def method_missing(phrase)
if @current_language.respond_to?(phrase)
@current_language.send(phrase)
else
super
end
end
end
module Language
module French
def self.bonjour
"Bonjour ! (from #{@current_language} instance variable)"
end
def self.au_revoir
"Au revoir."
end
end
module English
def self.hello
"Hello! (from #{@current_language} instance variable)"
end
def self.bye
"Bye."
end
end
end
class Person
attr_accessor :current_language
include LanguageProxy
def quit
@current_language = nil
end
end
person = Person.new
person.current_language # => nil
begin
p person.hello # => may raise a nice no method error
rescue
puts "Don't know hello"
end
person.current_language = Language::English
p person.hello # => "Hello! (from english instance variable)"
begin
p person.bonjour # => may also raise a no method error
rescue
puts "Don't know bonjour"
end
person.quit
person.current_language = Language::French
p person.bonjour # => "Bonjour ! (from french instance variable)"
Essentially, all we are doing here is creating a Proxy class to forward Person
's unknown messages to the language module stored in the Person
's @current_language
instance variable. The "trick" I used here is to make hello
, bye
, etc. module methods, not instance methods. Then, I assigned the actual module into @current_language
.
You'll also notice here that the @current_language
instance variable from Person
is not available in the Language
modules. It gets a little more tricky if you need the language methods to access those variables: the quick fix would probably be to just pass them as parameters.
If you really want to use symbols to denote the language, you'll have to do a little magic with Language.const_get
.
Output:
C:\temp\ruby>ruby person.rb Don't know hello "Hello! (from instance variable)" Don't know bonjour "Bonjour ! (from instance variable)"
Just to show that you could do this (not that you should):
include "A::B::C".split("::").inject{|p,c| (p.class == String ? Kernel.const_get(p) : p).const_get(c)}
精彩评论