Есть ли способ создать методы только для экземпляра класса Ruby изнутри этого экземпляра?
Пусть существует class Example
, определяемый как:
class Example
def initialize(test='hey')
self.class.send(:define_method, :say_hello, lambda { test })
end
end
При вызове Example.new; Example.new
получаем a warning: method redefined; discarding old say_hello
. Это, я заключаю, должно быть потому, что он определяет метод в реальном классе (что имеет смысл, из синтаксиса). И это, конечно, окажется катастрофическим, если в их методах есть несколько экземпляров Example
с разными значениями.
Есть ли способ создать методы только для экземпляра класса из этого экземпляра?
Ответы
Ответ 1
Вам нужно получить ссылку на экземпляр singleton class, класс, который содержит все экземпляры конкретных объектов, и определить метод на нем. В рубине 1.8, это выглядит немного грязно. (если вы найдете более чистое решение, дайте мне знать!)
Ruby 1.8
class Example
def initialize(test='hey')
singleton = class << self; self end
singleton.send :define_method, :say_hello, lambda { test }
end
end
Ruby 1.9, однако, обеспечивает гораздо более легкий способ.
Ruby 1.9
class Example
def initialize(test='hey')
define_singleton_method :say_hello, lambda { test }
end
end
Ответ 2
Прежде всего, небольшой совет:
self.class.send(:define_method, :say_hello, lambda { test })
Вы можете сделать этот взгляд немного приятнее, используя новый литерал proc в Ruby 1.9:
self.class.send(:define_method, :say_hello, -> { test })
Но вам это не нужно. Ruby имеет что-то, называемое блоками, которые в основном представляют собой фрагмент кода, который вы можете передать в качестве аргумента для метода. Фактически, вы уже использовали блоки, так как lambda
- это просто метод, который принимает блок как аргумент и возвращает Proc
. Тем не менее, define_method
уже принимает блок в любом случае, нет необходимости передавать блок в lambda
, который преобразует его в Proc
, который он передает на define_method
, который затем преобразует его обратно в блок:
self.class.send(:define_method, :say_hello) { test }
Как вы уже заметили, вы определяете метод в неправильном классе. Вы определяете его в классе Example
, так как внутри метода экземпляра, такого как initialize
, self
- текущий объект (т.е. ex1
или ex2
в примере @mikej), что означает, что self.class
ex1
, который равен Example
. Таким образом, вы переписываете один и тот же метод снова и снова.
Это приводит к следующему нежелательному поведению:
ex1 = Example.new('ex1')
ex2 = Example.new('ex2') # warning: method redefined; discarding old say_hello
ex1.say_hello # => ex2 # Huh?!?
Вместо этого, если вы хотите использовать одноэлементный метод, вам нужно определить его в классе singleton:
(class << self; self end).send(:define_method, :say_hello) { test }
Это работает по назначению:
ex1 = Example.new('ex1')
ex2 = Example.new('ex2')
ex1.say_hello # => ex1
ex2.say_hello # => ex2
В Ruby 1.9 существует метод, который делает это:
define_singleton_method(:say_hello) { test }
Теперь это работает так, как вы этого хотите, но здесь проблема более высокого уровня: это не Ruby-код. Это синтаксис Ruby, но это не Ruby-код, это Scheme.
Теперь, схема - блестящий язык, и писать код схемы в синтаксисе Ruby, конечно, не так уж плохо. Это чертовски чертит из написания Java или PHP-кода в синтаксисе Ruby, или, как в случае с вчерашним вопросом StackOverflow, код Fortran-57 в синтаксисе Ruby. Но это не так хорошо, как писать Ruby-код в синтаксисе Ruby.
Схема - функциональный язык. Функциональные языки используют функции (точнее, замыкания функций) для инкапсуляции и состояния. Но Ruby не является функциональным языком, это объектно-ориентированный язык, а языки OO используют объекты для инкапсуляции и состояния.
Итак, замыкания функций становятся объектами, а захваченные переменные становятся переменными экземпляра.
Мы также можем прийти к этому с совершенно другого ракурса: то, что вы делаете, заключается в том, что вы определяете метод singleton, который является методом, целью которого является определение поведения, характерного для одного объекта. Но вы определяете этот метод singleton для каждого экземпляра класса, и вы определяете один и тот же метод singleton для каждого экземпляра класса. У нас уже есть механизм определения поведения для каждого экземпляра класса: методы экземпляра.
Оба этих аргумента исходят из совершенно противоположных направлений, но они приходят в один и тот же пункт назначения:
class Example
def initialize(test='hey')
@test = test
end
def say_hello
@test
end
end
Ответ 3
Я знаю, что его попросили два года назад, но я хотел бы добавить еще один ответ. .instance_eval
поможет добавить методы к экземпляру объекта
string = "String"
string.instance_eval do
def new_method
self.reverse
end
end