为 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 模块中的每个方法调用执行代码的主要内容,如果未能解决你的问题,请参考以下文章