Как обезьяна исправляет рубиновый класс внутри метода

Я не могу исправить класс внутри тела метода.

В определении метода я пытаюсь использовать класс двумя способами:

1] Создайте экземпляр и используйте исходное определение метода в классе, который я использую.

2] Monkey исправляет (pverride) метод в классе и теперь использует экземпляр с новым определением метода.

По сути, я бы использовал оба вышеуказанных экземпляра класса в своей программе.

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

Вот небольшой макет:

class A

  def initialize
   do_something
  end

  def do something
     #implementation
  end

end

Теперь я хочу использовать A в одном и том же методе дважды, но один раз, используя модифицированную версию do_something. Вот как я пытаюсь это сделать:

def my_method

  orig_instance = A.new

  #patch the class
  Class A          # ERROR: CLASS DEF IN METHOD BODY
   class << self
     alias_method :old_do_something, :do_something

     def self.do_something
        # new implementation
     end
  end

  new_instance = A.new

  #restore method
   class << self
     alias_method :do_something,:old_do_something

     def self.do_something
        # new implementation
     end
  end        



end # end of method

Я получаю (ОШИБКА: CLASS DEF IN METHOD BODY), где я пытаюсь исправить класс, так как я пытаюсь изменить класс внутри метода.

Как мне добиться исправления обезьяной класса в методе?

Спасибо


person codeObserver    schedule 16.07.2013    source источник
comment
Вы получаете ошибку выше? Какая ошибка? И это c, а не C для класса.   -  person oldergod    schedule 16.07.2013
comment
Извините, если вы бегло просмотрели код, вы могли пропустить ОШИБКУ, отмеченную в коде. Напишу еще раз для ясности   -  person codeObserver    schedule 16.07.2013
comment
немного не связанный, но я уверен, что // не помечает комментарий в ruby   -  person Justin L.    schedule 16.07.2013
comment
Я изменил некоторые формулировки вопроса, чтобы сделать его более понятным. Надеюсь, это можно будет открыть повторно, поскольку это реальный вопрос программирования и то, что мне нужно для реального варианта использования. Спасибо   -  person codeObserver    schedule 21.07.2013
comment
В этом вопросе нет ничего неясного!!!   -  person Tofani    schedule 05.07.2016


Ответы (1)


Вместо того, чтобы использовать class Clazz; blabla; end для повторного открытия Clazz и исправления обезьяны, вы можете использовать Module#class_eval, Module#instance_eval и некоторые другие утилиты/методы метапрограммирования, чтобы проделать тот же трюк. И поскольку этот блок, принимаемый этими методами, не создает новых областей связывания, он более удобен в практике метапрограммирования.

def my_method
  puts ">> creating orig_instance"
  orig_instance = A.new

  puts ">> dump orig_instance"
  orig_instance.do_something

  new_do_something = lambda do
    puts "Modified A#do_something"
  end

  # monkey patch class A so that the modified version of do_something get called
  # during initialization of new_instance
  A.class_eval do
    alias_method :old_do_something, :do_something
    define_method :do_something, new_do_something
  end

  puts ">> creating new_instance"
  new_instance = A.new

  puts ">> dump before do_something gets restored"
  new_instance.do_something
  orig_instance.do_something

  # add singleton method for the special instance
  # so that the instance always calls the modified do_something
  new_instance_singleton = class << new_instance; self end
  new_instance_singleton.send :define_method, :do_something, new_do_something

  # restore the modified do_something
  # so that orig_instance and all other instances (except new_instance) have the original definition
  A.class_eval do
    alias_method :do_something, :old_do_something
  end
  puts ">> dump for final result"
  new_instance.do_something
  orig_instance.do_something
end

И вот результат вызова my_method:

>> creating orig_instance
Original A#do_something
>> dump orig_instance
Original A#do_something
>> creating new_instance
Modified A#do_something
>> dump before do_something gets restored
Modified A#do_something
Modified A#do_something
>> dump for final result
Modified A#do_something
Original A#do_something
person Arie Xiao    schedule 16.07.2013
comment
Спасибо @Ари. Не могли бы вы объяснить, почему необходимо добавить экземпляр singleton, учитывая, что класс A уже исправлен для использования нового определения метода. Мне непонятно, какой класс ‹‹ new_instance; self end делает особенно после того, как новый экземпляр уже создан. - person codeObserver; 21.07.2013
comment
@codeObserver Я думал, вы хотите внести изменения в метод экземпляра специального экземпляра (начиная с создания и продолжая в течение всего времени жизни экземпляра). Если вы восстановите метод без исправления обезьяны для специального new_instance, new_instance будет использовать исходную реализацию методов после восстановления. Патч обезьяны для экземпляра singleton позволяет использовать новый метод на протяжении всего времени существования new_instance. Если вы просто хотите изменить поведение во время создания, вы можете опустить патч для класса singleton. - person Arie Xiao; 23.07.2013
comment
@codeObserver class << new_instance; self end возвращает собственный класс (или одноэлементный класс, ~погуглите~) класса new_instance. Это особый класс, связанный с самим new_instance. Все методы, определенные в этом классе, будут видны только new_instance. Так что это способ исправить данный экземпляр, а не все экземпляры данного класса. - person Arie Xiao; 23.07.2013
comment
Я не знаю, подходит ли мой вопрос к этой теме, но я думаю, что #class_eval не работает для меня, так как я хочу переопределить метод класса, который использует константу класса: в вашем примере A.class_eval закрывается по области из #my_method, в то время как class A закрывает область класса A. Таким образом, если в моем переопределенном методе я попытаюсь получить доступ к константе (класса), определенной в A, я получу ошибку неинициализированной константы с #class_eval. Вот почему мне все еще интересно, как я могу исправить класс A внутри метода, как я сделал бы с повторным открытием class A? - person Andreas Rayo Kniep; 13.01.2016