我希望能够在类的方法的开头和结尾插入一些代码。我也想避免重复。
我发现this answer很有帮助,但是重复并没有帮助。
class MyClass
def initialize
[:a, :b].each{ |method| add_code(method) }
end
def a
sleep 1
"returning from a"
end
def b
sleep 1
"returning from b"
end
private
def elapsed
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
block_value = yield
finish = Process.clock_gettime(Process::CLOCK_MONOTONIC)
puts "elapsed: #{finish - start} seconds, block_value: #{block_value}."
block_value
end
def add_code(meth)
meth = meth.to_sym
self.singleton_class.send(:alias_method, "old_#{meth}".to_sym, meth)
self.singleton_class.send(:define_method, meth) do
elapsed do
send("old_#{meth}".to_sym)
end
end
end
end
以上方法确实有效,但是什么是更优雅的解决方案?我希望能够例如将attr_add_code
放在类定义的开头,并列出要添加代码的方法,或者甚至指定将其添加到所有公共方法。 / p>
注意:self.singleton_class
只是一种解决方法,因为我在初始化期间添加了代码。
答案 0 :(得分:2)
如果通过重复来表示要检测的方法列表,则可以执行以下操作:
module Measure
def self.prepended(base)
method_names = base.instance_methods(false)
base.instance_eval do
method_names.each do |method_name|
alias_method "__#{method_name}_without_timing", method_name
define_method(method_name) do
t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
public_send("__#{method_name}_without_timing")
t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
puts "Method #{method_name} took #{t2 - t1}"
end
end
end
end
end
class Foo
def a
puts "a"
sleep(1)
end
def b
puts "b"
sleep(2)
end
end
Foo.prepend(Measure)
foo = Foo.new
foo.a
foo.b
# => a
# => Method a took 1.0052679998334497
# => b
# => Method b took 2.0026899999938905
主要变化是,我使用prepend
,并且在prepended
回调中,您可以找到在类中使用instance_methods(false)
定义的方法列表,其中的false
参数指示不应该考虑祖先。
答案 1 :(得分:1)
代替使用方法别名(在我看来,自从引入Module#prepend
以来,它已经成为过去),我们可以在匿名模块之前添加一个匿名模块,该模块为要测量的类的每个实例方法提供一个方法。这将导致调用MyClass#a
来调用此匿名模块中的方法,该方法将测量时间并仅依靠super
来调用实际的MyClass#a
实现。
def measure(klass)
mod = Module.new do
klass.instance_methods(false).each do |method|
define_method(method) do |*args, &blk|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
value = super(*args, &blk)
finish = Process.clock_gettime(Process::CLOCK_MONOTONIC)
puts "elapsed: #{finish - start} seconds, value: #{value}."
value
end
end
end
klass.prepend(mod)
end
或者,您可以使用class_eval
,它也更快,并且允许您仅调用super
而无需指定任何参数来转发方法调用中的所有参数,而{{ 1}}。
define_method
要使用此功能,只需执行以下操作:
def measure(klass)
mod = Module.new do
klass.instance_methods(false).each do |method|
class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{method}(*)
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
value = super
finish = Process.clock_gettime(Process::CLOCK_MONOTONIC)
puts "elapsed: \#{finish - start} seconds, value: \#{value}."
value
end
CODE
end
end
klass.prepend(mod)
end
答案 2 :(得分:0)
您似乎正在尝试进行一些基准测试。您是否签出了benchmark library?在标准库中。
require 'benchmark'
puts Benchmark.measure { MyClass.new.a }
puts Benchmark.measure { MyClass.new.b }
答案 3 :(得分:0)
另一种可能性是创建一个包装类,如下所示:
class Measure < BasicObject
def initialize(target)
@target = target
end
def method_missing(name, *args)
t1 = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
target.public_send(name, *args)
t2 = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
::Kernel.puts "Method #{name} took #{t2 - t1}"
end
def respond_to_missing?(*args)
target.respond_to?(*args)
end
private
attr_reader :target
end
foo = Measure.new(Foo.new)
foo.a
foo.b