代码之家  ›  专栏  ›  技术社区  ›  Ian Terrell

如何用类似宏的元编程方法扩展Ruby模块?

  •  6
  • Ian Terrell  · 技术社区  · 15 年前

    考虑下面的扩展(多年来由几个Rails插件推广的模式):

    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)
    

    这是真的,即使你明确地包括 Extension 进入之内 Module 具有 Module.send(:include, Extension) .

    对于单个模块,您可以手动包含扩展并获得相同的效果:

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

    但是如何向所有Ruby模块添加类似宏的方法呢?

    1 回复  |  直到 15 年前
        1
  •  22
  •   Jörg W Mittag    15 年前

    考虑下面的扩展(多年来由几个Rails插件推广的模式)

    这是 这是一种模式,并没有“普及”。这是一个 反模式 那是 货物崇拜 由1337 php h4x0rz谁不知道鲁比。谢天谢地,很多(全部?)由于Yehuda Katz、Carl Lerche和其他人的强硬言论,这种反模式的例子已经从Rails 3中消除。Yehuda甚至在最近关于清理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行代码的对比,而这10行中的0行是元编程或钩子,甚至是远程复杂的任何代码。

    您的代码和我的代码之间的唯一区别是,在您的代码中,mixin显示在继承层次结构中,因此调试起来稍微容易一点,因为实际上 看见 添加了一些东西 对象 . 但这很容易解决:

    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用户都无法找出原因。