为 Ruby 模块中的每个方法调用执行代码

Posted

技术标签:

【中文标题】为 Ruby 模块中的每个方法调用执行代码【英文标题】:Executing code for every method call in a Ruby module 【发布时间】:2011-07-27 16:19:14 【问题描述】:

我正在用 Ruby 1.9.2 编写一个模块,它定义了几种方法。当调用这些方法中的任何一个时,我希望它们中的每一个都首先执行某个语句。

module MyModule
  def go_forth
    a re-used statement
    # code particular to this method follows ...
  end

  def and_multiply
    a re-used statement
    # then something completely different ...
  end
end

但我想避免将 a re-used statement 代码明确地放在每个方法中。有办法吗?

(如果重要,a re-used statement 将拥有每个方法,在调用时打印自己的名称。它将通过puts __method__ 的某些变体来实现。)

【问题讨论】:

这个问题与 Ruby 1.9.2 有关。但是这些天来,如果您刚刚发现这个问题,那么您可能正在使用 Ruby 2+。在 Ruby 2+ 中,prepend 是一个不错的选择。参见,例如,***.com/questions/4219277/… 【参考方案1】:

像这样:

module M
  def self.before(*names)
    names.each do |name|
      m = instance_method(name)
      define_method(name) do |*args, &block|  
        yield
        m.bind(self).(*args, &block)
      end
    end
  end
end

module M
  def hello
    puts "yo"
  end

  def bye
    puts "bum"
  end

  before(*instance_methods)  puts "start" 
end

class C
  include M
end

C.new.bye #=> "start" "bum"
C.new.hello #=> "start" "yo"

【讨论】:

+1 喜欢。但是 Ruby 1.8.7 不支持? NoMethodError: undefined method before' 代表 M:Module` @fl00r,要让它在 1.8.7 中工作,您只需要更改 proc 调用语法,我使用的是 .()(仅限 1.9)而不是 .call() 嗨,你能解释一下 m.bind(self).(*args, &block) 到底是做什么的吗?我已经从谷歌搜索了 ruby​​ 文档和许多页面,但我仍然不知道它是如何工作的。非常感谢您的帮助。 @reizals 见ruby-doc.org/core-2.1.2/UnboundMethod.html#method-i-bind。 (回复仅供大家参考。) 等等,如果你在 C 类定义中使用了 before 方法,为什么这不起作用?如果您将 before(*instance_methods) puts "start " 移至 C 类,我将得到 <class:C>': undefined method before' for C:Class (NoMethodError)`【参考方案2】:

这正是创建 aspector 的目的。

使用 aspector,您无需编写样板元编程代码。您甚至可以更进一步,将通用逻辑提取到单独的方面类中并独立测试。

require 'aspector'

module MyModule
  aspector do
    before :go_forth, :add_multiply do
      ...
    end
  end

  def go_forth
    # code particular to this method follows ...
  end

  def and_multiply
    # then something completely different ...
  end
end

【讨论】:

【参考方案3】:

您可以通过代理模块使用method_missing 来实现它,如下所示:

module MyModule

  module MyRealModule
    def self.go_forth
      puts "it works!"
      # code particular to this method follows ...
    end

    def self.and_multiply
      puts "it works!"
      # then something completely different ...
    end
  end

  def self.method_missing(m, *args, &block)
    reused_statement
    if MyModule::MyRealModule.methods.include?( m.to_s )
      MyModule::MyRealModule.send(m)
    else
      super
    end
  end

  def self.reused_statement
    puts "reused statement"
  end
end

MyModule.go_forth
#=> it works!
MyModule.stop_forth
#=> NoMethodError...

【讨论】:

【参考方案4】:

你可以通过元编程技术做到这一点,这里有一个例子:

module YourModule
  def included(mod)
    def mod.method_added(name)
      return if @added 
      @added = true
      original_method = "original #name"
      alias_method original_method, name
      define_method(name) do |*args|
        reused_statement
        result = send original_method, *args
        puts "The method #name called!"
        result
      end
      @added = false
    end
  end

  def reused_statement
  end
end

module MyModule
  include YourModule

  def go_forth
  end

  def and_multiply
  end
end

仅适用于 ruby​​ 1.9 及更高版本

更新:也不能使用块,即实例方法中没有产量

【讨论】:

【参考方案5】:

我不知道,为什么我被否决了 - 但一个合适的 AOP 框架比元编程黑客要好。这就是 OP 想要实现的目标。

http://debasishg.blogspot.com/2006/06/does-ruby-need-aop.html

另一种解决方案可能是:

module Aop
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def before_filter(method_name, options = )
      aop_methods = Array(options[:only]).compact
      return if aop_methods.empty?
      aop_methods.each do |m|
        alias_method "#m_old", m
        class_eval <<-RUBY,__FILE__,__LINE__ + 1
          def #m
            #method_name
            #m_old
          end
        RUBY
      end
    end
  end
end

module Bar
  def hello
    puts "Running hello world"
  end
end

class Foo
  include Bar
  def find_hello
    puts "Running find hello"
  end
  include Aop
  before_filter :find_hello, :only => :hello
end

a = Foo.new()
a.hello()

【讨论】:

【参考方案6】:

元编程是可能的。

另一种选择是Aquarium。 Aquarium 是一个为 Ruby 实现面向切面编程 (AOP) 的框架。 AOP 允许您跨普通对象和方法边界实现功能。您的用例,对每个方法应用预操作,是 AOP 的一项基本任务。

【讨论】:

我也不知道为什么这被否决了。也许是因为没有示例,只有一个链接。 对随机库的链接投反对票,但没有解释为什么我应该点击链接

以上是关于为 Ruby 模块中的每个方法调用执行代码的主要内容,如果未能解决你的问题,请参考以下文章

从 Ruby 中的继承类调用方法

如何运行一个ruby类 中的方法

我可以在 Ruby 模块上调用实例方法而不包含它吗?

ruby中的作用域和代码块

Ruby mixins 和调用超级方法

有没有办法从 Ruby 中的实例调用私有类方法?