2010-06-10 50 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