SCoon

Monkey See, Monkey Patch

Оценить эту запись
Читая чужие скрипты, постоянно встречаю удивляющий меня фрагмент. На примере из все того же "Scripting in RGSS Ruby for Intermediate and Experts":

Код:
class Scene_Map
  alias_method :s____partycycling_scnmap_update, :update
  def update
    if Input.trigger?(Input::L)
      $game_party.shift_forward
    elsif Input.trigger?(Input::R)
      $game_party.shift_backwards
    end
    s____partycycling_scnmap_update
  end
end
(Это "исходный" вариант -- выполненная Blizzard'ом переделка не заслуживает упоминания ввиду полной убогости).

Что мы здесь видим? Каждый раз автор такого фрагмента заново вымучивает уникальный префикс, создает алиас метода и переопределяет его. Каждый раз эта пляска с бубном мешает понять, ради чего все это затеяно. Помня о том, что мы имеет дело с Ruby, который очень доброжелателен к программистам, попробуем записать то же более прозрачно.

В чем суть этого кода? Очень просто: выполнить ДО метода update некоторый нужный нам код. Вот так было бы понятнее, не правда ли?

Код:
class Scene_Map
  before :update do
    if Input.trigger?(Input::L)
      $game_party.shift_forward
    elsif Input.trigger?(Input::R)
      $game_party.shift_backwards
    end
  end
end
Читается очевидно: "до update сделать это". В таком виде понять смысл действий проще, не правда ли? Другой характерный пример -- дополнительная инициализация:

Код:
class Game_System
  after :initialize do
    @gold_log = []
  end
end
Поскольку подобный код нужен часто, а слово initialize лично у меня -- источник постоянных опечаток, хорошо бы иметь возможность писать еще короче и понятнее:

Код:
class Game_System
  after_init do
    @gold_log = []
  end
end
Кода меньше, код понятнее. Как следствие -- пишем быстрее, читаем быстрее, делаем меньше ошибок.

Одна беда -- штатно Ruby этого не умеет. Но мы ему поможем. Самый простой способ -- дописать реализацию before, after и т.п. непосредственно в Class. При этом мы не будем изобретать велосипед и внутри, "под ковром", будем использовать все тот же monkey patching. Прежде всего, нам потребуется метод для формирования уникальных алиасов:

Код:
class Class
  private
  @@alias_index = 0
  def create_unique_alias(method)
    @@alias_index += 1
    original = "__alias#{@@alias_index}_#{method}".to_sym
    class_eval %Q{
      alias_method :#{original}, :#{method}
    }
    original
  end
end
Здесь все просто: алиас формируется добавлением префикса к имени метода. Поскольку мы имеем право захотеть проалиасить метод несколько раз -- добавляем еще и инкрементируемый счетчик. Итак, на входе символ с именем метода, на выходе -- символ с созданным уникальным алиасом. Теперь мы можем написать что-нибудь полезное:

Код:
def after(method, &block)
  original = create_unique_alias(method)
  define_method method do |*args|
    self.__send__(original, *args)
    instance_exec(*args, &block)
  end
end
Код, в основом, простой: имея свежесозданный алиас, переопределяем метод, а внутри него вызываем (через __send__) старый метод по алиасу. Затем вызываем блок с кодом, который нам передали (он получит параметры, с которыми был вызван метод). Важный нюанс -- вызов через instance_exec. Дело в том, что параметр block содержит блок кода, определенный в контексте класса, а не экземпляра. Если мы просто напишем "block.call(*args)", то не сможем достучаться до переменных и методов объекта.

На основе after реализуем специальный случай -- добавление кода к конструктору:

Код:
def after_init(&block)
  after :initialize, &block
end
Реализация before практически идентична реализации after:

Код:
def before(method, &block)
  original = create_unique_alias(method)
  define_method method do |*args|
    instance_exec(*args, &block)
    self.__send__(original, *args)
  end
end
Разница лишь в порядке вызовов. В большинстве случаев before и after будет достаточно. Однако, изредка требуется вызвать старый метод из середины своего кода, да еще несколько раз или с измененными параметрами. Для таких случаев мы реализуем instead_of, который дает нужную свободу выбора момента для вызова оригинального метода и позволяет подменять его параметры:

Код:
def instead_of(method, &block)
  original = create_unique_alias(method)
  define_method method do |*args|
    original_block = proc { |*args| 
      self.__send__(original, *args)
    }
    actual_args = [ original_block ] + args
    instance_exec(*actual_args, &block)
  end
end
Здесь есть интересный нюанс: чтобы наш блок кода мог вызвать исходный метод, мы завернем его в процедуру и передадим в блок первым параметром. Пример использования:

Код:
class Game_Party
  instead_of :gain_gold do |original, amount|
    adjusted_amount = [100, amount].min
    original.call(adjusted_amount)
  end
end
Ruby -- красивый и удобный язык. Используйте его, чтобы сделать свою жизнь приятнее!

Отправить "Monkey See, Monkey Patch" в Digg Отправить "Monkey See, Monkey Patch" в del.icio.us Отправить "Monkey See, Monkey Patch" в StumbleUpon Отправить "Monkey See, Monkey Patch" в Google Отправить "Monkey See, Monkey Patch" в VKontakte Отправить "Monkey See, Monkey Patch" в Facebook

Категории
Без категории

Комментарии

  1. Аватар для Anxel
    Я ничего не понял, но выглядит круто xDD
  2. Аватар для SCoon
    Если есть желание попробовать -- вот весь код целиком: http://pastebin.com/zWQJw0Hs
  3. Аватар для Anxel
    не, я со скриптингом и рядом не стоял.
  4. Аватар для sxdemon
    А не желаете ли вы поучаствовать в создании общей боевой системы ? Ваше участие и опыт, позволил бы создавать невероятные вещи! )
  5. Аватар для SCoon
    При хорошем знании Ruby я пока весьма поверхностно знаком с RGSS. Так что непосредственно сейчас чудес не будет. Но если будут какие-то конкретные вопросы/предложения -- обращайтесь.
  6. Аватар для Рыб
    SCoon, а какие книги можно посоветовать для программиста немного знакомого с ООП, но нефига не знающего в Ruby?

    И не могли, бы Вы продублировать ваши посты вот в разделе Статьи. Если затеряются такие вещи, как ваши статьи
  7. Аватар для SCoon
    Однозначно рекомендую "Eloquent Ruby" -- чтобы понять философию языка. Если есть много свободного времени -- можно взять "Programming Ruby: The Pragmatic Programmers Guide", изданную в серии "The Pragmatic Programmers". Читать ее нужно выборочно. Есть ее онлайновый вариант, но он не совпадает с печатным.

    В статьи запощенный выше текст вряд ли стоит переносить как есть. Возможно, имеет смысл написать отдельную статью про метапрограммирование на Ruby.
  8. Аватар для Рыб
    Спасибо большое за книги.
    Да скорее необходим, цикл статей "Метопрограммирование для чайников" ))