Как реализуется метод" public/protected/private " и как я могу его эмулировать?


В ruby вы можете сделать следующее:

class Thing
  public
  def f1
    puts "f1"
  end

  private
  def f2
    puts "f2"
  end

  public
  def f3
    puts "f3"
  end

  private
  def f4
    puts "f4"
  end
end

Где теперь f1 и f3 и public, f2 и f4-частные. Что происходит внутри, что позволяет вызвать метод класса, который затем изменяет определение метода? Как я могу реализовать ту же функциональность (якобы для создания собственных Java-подобных аннотаций)

Например...

class Thing
  fun
  def f1
    puts "hey"
  end

  notfun
  def f2
    puts "hey"
  end
end

И fun и notfun изменили бы следующие определения функций.

Спасибо

3 5

3 ответа:

Иногда вы можете засунуть Рубин в чашку эспрессо. Давайте посмотрим, как это сделать.

Вот модуль FunNotFun...

module FunNotFun

  def fun
    @method_type = 'fun'
  end

  def notfun
    @method_type = 'not fun'
  end

  def method_added(id)
    return unless @method_type
    return if @bypass_method_added_hook
    orig_method = instance_method(id)
    @bypass_method_added_hook = true
    method_type = @method_type
    define_method(id) do |*args|
      orig_method.bind(self).call(*args).tap do
        puts "That was #{method_type}"
      end
    end
    @bypass_method_added_hook = false
  end

end

... который можно использовать для расширения класса ...

class Thing

  extend FunNotFun

  fun
  def f1
    puts "hey"
  end

  notfun
  def f2
    puts "hey"
  end
end

... с таким результатом:

Thing.new.f1
# => hey
# => That was fun

Thing.new.f2
# => hey
# => That was not fun
Но смотрите ниже строки для лучшего способа.

Аннотации (см. ответ normalocity) менее сложны и, будучи обычной идиомой Ruby, легче передадут намерение вашего кода. Вот как это сделать с аннотациями:

module FunNotFun

  def fun(method_id)
    wrap_method(method_id, "fun")
  end

  def notfun(method_id)
    wrap_method(method_id, "not fun")
  end

  def wrap_method(method_id, type_of_method)
    orig_method = instance_method(method_id)
    define_method(method_id) do |*args|
      orig_method.bind(self).call(*args).tap do
        puts "That was #{type_of_method}"
      end
    end
  end

end

В использовании, аннотация появляется после определения метода, а не раньше:

class Thing

  extend FunNotFun

  def f1
    puts "hey"
  end
  fun :f1

  def f2
    puts "hey"
  end
  notfun :f2

end

Результат тот же:

Thing.new.f1
# => hey
# => That was fun

Thing.new.f2
# => hey
# => That was not fun

Похоже, что вы хотите написать расширения для самого языка Ruby, что вполне возможно. Это не то, что можно объяснить кратко, но эта ссылка должна помочь вам начать:

Http://ruby-doc.org/docs/ProgrammingRuby/html/ext_ruby.html

Эта ссылка, имеющая отношение к аннотациям в Ruby, также может быть полезной / актуальной:

Http://martinfowler.com/bliki/RubyAnnotations.html

Вот чисто рубиновое решение, которое поможет вам двигаться в правильном направлении. Он зависит от method_added. Будьте осторожны, чтобы избежать рекурсии с помощью предложения guard.

module Annotations
  def fun
    @state = :fun
  end

  def not_fun
    @state = :not_fun
  end

  def inject_label(method_name)
    state = @state
    define_method(:"#{method_name}_with_label") do |*args, &block|
      puts "Invoking #{method_name} in state #{state}"
      send(:"#{method_name}_without_label", *args, &block)
     end

    alias_method :"#{method_name}_without_label", :"#{method_name}"
    alias_method :"#{method_name}", :"#{method_name}_with_label"
  end

  def self.extended(base)
    base.instance_eval do
      def self.method_added(method_name)
        return if method_name.to_s =~ /_with(out)?_label\Z/
        @seen ||= {}
        unless @seen[method_name]
          @seen[method_name] = true
          inject_label(method_name)
        end
      end
    end
  end
end

class Foo
  extend Annotations

  fun

  def something
    puts "I'm something"
  end

  not_fun

  def other
    puts "I'm the other"
  end
end