2010-04-29 45 views
0

我想允许对象(从类实例化)讲一种语言(这是存储在语言模块的公共方法的集合):红宝石 - 协议(含模块)之间进行动态切换

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 

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

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

问候

回答

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

从本质上讲,所有我们在这里做的是创建一个代理类Person的来历不明的邮件存储在Person语言模块“向S @current_language实例变量。我在这里使用的“技巧”是制作hello,bye模块方法,而不是实例方法。然后,我将实际模块分配到@current_language

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

如果你真的想用符号来表示语言,你必须用Language.const_get来做一点小小的魔法。

输出:

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

谢谢你马克对这个技巧。那工作得很好!然而,我想这个模块的方法被动态地(有一种动态包含)合并到一个对象中,然后(例如)用person.public_send()访问它们。因此,实例变量可以从新方法访问。 – 2010-04-29 18:50:17

0

只是为了显示你可以做到这一点(不是你应该):

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

感谢您的回答。但我不太了解你的代码。 – 2010-04-29 21:27:51