2011-10-12 85 views
5

在Ruby中,你可以這樣做:「public/protected/private」方法是如何實現的?如何模擬它?

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和公衆,F2和F4是私人的。內部發生了什麼,允許你調用一個類方法,然後改變方法定義?我如何能實現相同的功能(表面上是爲了創造自己的Java註解一樣)

例如...

class Thing 
    fun 
    def f1 
    puts "hey" 
    end 

    notfun 
    def f2 
    puts "hey" 
    end 
end 

和樂趣和notfun會改變下列函數的定義。

感謝

回答

8

你有時可以把紅寶石推到espressso杯子裏。讓我們看看如何。

這裏有一個模塊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 
+0

啊,這是我想到的更多 –

+0

是第一種線程安全的方法嗎? –

+1

@Semyon,如果您有多個線程同時向同一個類添加方法,請不要。但是幾乎總是隻通過一個線程添加方法。 –

1

聽起來像是你要編寫擴展Ruby編程語言本身,這是可能的。這是不是可以簡單地解釋,但是這個環節應該讓你開始:

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

此參照中,具有與Ruby的註釋做,也可能會有所幫助/相關:

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

+1

真的有必要延長紅寶石可能嗎?感覺應該可以通過一些聰明的元編程或其他東西來實現,但我想這可能並非如此。 –

1

這是一個純粹的紅寶石解決方案,讓您朝着正確的方向前進。它取決於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