Ruby - 在协议之间动态切换(使用模块)

时间:2010-04-29 15:49:13

标签: ruby interface include module protocols

我想允许 person 对象(来自 Person 类的实例)说一种语言(这是一种存储在语言中的公共方法的集合模块):

class Person
  attr_accessor :current_language

  def quit
    # Unselect the current language, if any:
    @current_language = nil
  end
end

假设语言如下:

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

使用示例:

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)"

正如您所看到的,语言就像协议一样。因此,一个人可以打开特定协议,但一次只能打开一个协议。

出于模块化原因,将每种语言存储到模块中都很友好。所以我认为这种方式更合乎逻辑的Ruby方式,不是吗。

但是,我认为不可能写出这样的东西:

class Person
  include "Language::#{@current_language}" unless @current_language.nil?
end

根据你的说法,这样做的最佳做法是什么?

欢迎任何评论和消息。谢谢。

此致

2 个答案:

答案 0 :(得分:0)

如果你正确安排你的模块,你可以在Ruby中非常优雅地做到这一点。

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)"

基本上,我们在这里所做的就是创建一个Proxy类,将Person的未知消息转发到存储在Person的{​​{1}}实例变量中的语言模块。我在这里使用的“技巧”是制作@current_languagehello模块方法,而不是实例方法。然后,我将实际模块分配到bye

您还会注意到@current_language模块中的@current_language实例变量不可用Person。如果你需要语言方法来访问这些变量,它会变得有点棘手:快速修复可能只是将它们作为参数传递。

如果您真的想使用符号来表示语言,那么您必须使用Language做一些魔术。

输出:

C:\temp\ruby>ruby person.rb
Don't know hello
"Hello! (from  instance variable)"
Don't know bonjour
"Bonjour ! (from  instance variable)"

答案 1 :(得分:0)

只是为了证明你可以这样做(不是你应该这样做):

include "A::B::C".split("::").inject{|p,c| (p.class == String ? Kernel.const_get(p) : p).const_get(c)}