Ruby: автоматическая упаковка методов в триггеры событий - PullRequest
2 голосов
/ 29 ноября 2011

Вот что я имею / хочу:

module Observable
  def observers; @observers; end

  def trigger(event, *args)
    good = true
    return good unless (@observers ||= {})[event]
    @obersvers[event].each { |e| good = false and break unless e.call(self, args) }
    good
  end

  def on(event, &block)
    @obersvers ||= {}
    @obersvers[event] ||= []
    @observers[event] << block
  end

end

class Item < Thing
  include Observable
  def pickup(pickuper)
    return unless trigger(:before_pick_up, pickuper)

    pickuper.add_to_pocket self

    trigger(:after_pick_up, pickuper)
  end

  def drop(droper)
    return unless trigger(:before_drop, droper)

    droper.remove_from_pocket self

    trigger(:after_drop, droper)
  end

  # Lots of other methods
end

# How it all should work
Item.new.on(:before_pickup) do |item, pickuper| 
  puts "Hey #{pickuper} thats my #{item}"
  return false # The pickuper never picks up the object
end

Начав пытаться создать игру в Ruby, я подумал, что было бы здорово, если бы она основывалась на Observers and Events. Проблема в том, что писать все эти триггеры, кажется, пустая трата, так как кажется, что много дублированного кода. Я чувствую, что должен быть какой-то метод метапрограммирования, чтобы обернуть методы функциональностью.

Идеальный Sceanrio:

class CustomBaseObject
   class << self
     ### Replace with correct meta magic
     def public_method_called(name, *args, &block)
       return unless trigger(:before_+name.to_sym, args)
       yield block
       trigger(:after_+name.to_sym, args)
     end
     ###
   end
end

А потом у меня все мои объекты наследуются от этого Class.

Я все еще плохо знаком с более продвинутыми предметами метапрограммирования Руби, поэтому любые знания об этом типе вещей были бы удивительными.

1 Ответ

6 голосов
/ 30 ноября 2011

Есть несколько способов сделать это с помощью магии метапрограммирования. Например, вы можете определить метод следующим образом:

def override_public_methods(c)
  c.instance_methods(false).each do |m|
    m = m.to_sym
    c.class_eval %Q{
      alias #{m}_original #{m}
      def #{m}(*args, &block)
        puts "Foo"
        result = #{m}_original(*args, &block)
        puts "Bar"
        result
      end
    }
  end
end

class CustomBaseObject
  def test(a, &block)
    puts "Test: #{a}"
    yield
  end
end

override_public_methods(CustomBaseObject)

foo = CustomBaseObject.new
foo.test(2) { puts 'Block!' }
# => Foo
     Test: 2
     Block!
     Bar

В этом случае вы определяете все необходимые методы, определенные в классе, используя instance_methods, а затем переопределяете их.

Другой способ - использовать так называемые методы «ловушки»:

module Overrideable
  def self.included(c)
    c.instance_methods(false).each do |m|
      m = m.to_sym
      c.class_eval %Q{
        alias #{m}_original #{m}
        def #{m}(*args, &block)
          puts "Foo"
          result = #{m}_original(*args, &block)
          puts "Bar"
          result
        end
      }
    end
  end
end

class CustomBaseObject
  def test(a, &block)
    puts "Test: #{a}"
    yield
  end

  include Overrideable
end

Хук included, определенный в этом модуле, вызывается, когда вы include этот модуль. Для этого вам необходимо include модуль в конце определения класса, потому что included должен знать обо всех уже определенных методах. Я думаю, что это довольно некрасиво:)

...