2010-06-10 75 views
5

考慮以下擴展名(由幾個Rails插件多年來推廣的模式):如何用類宏編程方法擴展Ruby模塊?

module Extension 
    def self.included(recipient) 
    recipient.extend ClassMethods 
    recipient.send :include, InstanceMethods 
    end 

    module ClassMethods 
    def macro_method 
     puts "Called macro_method within #{self.name}" 
    end 
    end 

    module InstanceMethods 
    def instance_method 
     puts "Called instance_method within #{self.object_id}" 
    end 
    end 
end 

如果你想揭露這對每一個類,你可以做到以下幾點:

Object.send :include, Extension 

現在您可以定義任何類並使用宏方法:

class FooClass 
    macro_method 
end 
#=> Called macro_method within FooClass 

而實例可以使用實例方法:

FooClass.new.instance_method 
#=> Called instance_method within 2148182320 

但即使Module.is_a?(Object),您不能在模塊中使用宏方法。

module FooModule 
    macro_method 
end 
#=> undefined local variable or method `macro_method' for FooModule:Module (NameError) 

這是真實的,即使你明確地包含原始ExtensionModuleModule.send(:include, Extension)

各個模塊可以包括手動擴展和獲得同樣的效果:

module FooModule 
    include Extension 
    macro_method 
end 
#=> Called macro_method within FooModule 

但你如何添加類似於宏的方法對所有的Ruby模塊?

回答

22

考慮以下擴展名(由幾個Rails插件多年來推廣的模式)

這是不是的模式,它是不是「普及」。這是一個反模式那是貨櫃 1337 PHP h4X0rZ誰不知道Ruby。值得慶幸的是,由於Yehuda Katz,Carl Lerche和其他人的努力,這個反模式的許多(全部?)實例已經從Rails 3中消除了。耶胡達甚至在他最近的關於清理Rails代碼庫的談話中使用了幾乎完全相同的代碼作爲反例,並且他寫了an entire blog post就是這個反模式。

如果你想揭露這對每一個類,你可以做到以下幾點:

Object.send :include, Extension 

如果你想將其添加到Object反正,那麼爲什麼不這樣做:

class Object 
    def instance_method 
    puts "Called instance_method within #{inspect}" 
    end 
end 

但你如何添加類似於宏的方法對所有的Ruby模塊?

簡單:通過將它們添加到Module

class Module 
    def macro_method 
    puts "Called macro_method within #{inspect}" 
    end 
end 

這一切都只是工作:

class FooClass 
    macro_method 
end 
#=> Called macro_method within FooClass 

FooClass.new.instance_method 
#=> Called instance_method within #<FooClass:0x192abe0> 

module FooModule 
    macro_method 
end 
#=> Called macro_method within FooModule 

這只是10行代碼對你的16,並恰好爲0的那些10行是元編程或鉤子或甚至是遠程複雜的任何東西。

你的代碼和我之間唯一的區別是,在你的代碼中,混入繼承層次結構中顯示出來,所以它是一點點更容易調試,因爲你實際上看到的東西加入到Object。但是,這是很容易固定:

module ObjectExtensions 
    def instance_method 
    puts "Called instance_method within #{inspect}" 
    end 
end 

class Object 
    include ObjectExtensions 
end 

module ModuleExtensions 
    def macro_method 
    puts "Called macro_method within #{inspect}" 
    end 
end 

class Module 
    include ModuleExtensions 
end 

現在,我與你的代碼捆綁在16行,但我認爲,我的比你的更簡單,特別是考慮到你不工作,無論是你還是我也不幾乎190000 StackOverflow用戶可以找出原因。

+1

*有*的博客文章。我知道我最近在某處看到過它。仍然好奇爲什麼其他策略不起作用,我不認爲它是一個嚴重的反模式,儘管是一個小小的缺陷,但我確實同意,從根本上說,「在包含延伸行爲時沒有意義Ruby提供了兩者。「 – 2010-06-11 05:33:17

+3

+1爲「貨物裝箱」。 – 2011-07-31 16:48:07

+0

有沒有像你一樣定義「macro_method」的方法,但能夠選擇哪些類可以訪問它? – 2013-09-18 00:19:44