开发者

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)}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜