2011-01-26 2 views
3

Скажут, у меня есть следующие классы:Определить методы из шаблона во время выполнения в рубине

class Foo 
    attr_accessor :name, :age 
end 

class Bar 
    def initialize(name) 
     @foo = Foo.new 
     @foo.name = name 
    end 
end 

Я хотел бы определить аксессор на баре, который является простым псевдонимом foo.name. Достаточно легко:

def name 
    @foo.name 
end 

def name=(value) 
    @foo.name = value 
end 

Имея только одно свойство, это достаточно легко. Однако, скажем, Foo предоставляет несколько свойств, которые я хочу открыть через Bar. Вместо того, чтобы определять каждый метод вручную, я хочу сделать что-то вроде этого, хотя я знаю, что синтаксис не прав:

[:name, :age].each do |method| 
    def method 
    @foo.method 
    end 

    def method=(value) 
    @foo.method = value 
    end 
end 

Итак ... что такое правильный способ определения методов, как это?

+1

Btw, ваш пример кода на самом деле не работает, потому что 'foo' является локальной переменной и выходит за пределы области действия в конце метода' initialize'. – sepp2k

+0

@ sepp2k: Да, глупо меня –

ответ

1
[:name, :age].each do |method| 
    define_method(method) do 
    foo.send(method) 
    end 

    define_method("#{method}=") do |value| 
    foo.send("#{method}=", value) 
    end 
end 
+0

Сладкий, именно то, что я искал. Из любопытства возникает проблема с использованием foo.send? Я надеялся определить тело метода как строку, заменяющую «метод» именем действия. –

+0

Да, 'Object # send 'работает медленнее. Неважно, зависит ли это от того, как часто вы отправляете методы таким образом. Чтобы определить тело метода как 'String', вам нужно использовать один из методов eval, который медленнее. Он также может иметь ряд последствий для безопасности. –

2

Чтобы динамически определить метод, вы можете использовать define_method, который принимает имя метода как символ или строковый аргумент.

Чтобы динамически вызвать метод, вы можете использовать send, который также принимает имя метода как символ или строку.

[:name, :age].each do |method| 
    define_method(method) do 
    @foo.send(method) 
    end 

    define_method("#{method}=") do |value| 
    @foo.send("#{method}=", value) 
    end 
end 
+0

Ба, ты избил меня. Мне нравится, как наш код почти идентичен. :-) –

1

Смотрите Delegator class в стандартной библиотеке Ruby, в зависимости от того, сколько методы, которые вы хотите передать.

Смежные вопросы