编辑:我略微更改了规范,以便更好地匹配我想象的事情。
好吧,我真的不想伪造C#属性,我想要一对一并支持AOP。
鉴于该计划:
class Object
def Object.profile
# magic code here
end
end
class Foo
# This is the fake attribute, it profiles a single method.
profile
def bar(b)
puts b
end
def barbar(b)
puts(b)
end
comment("this really should be fixed")
def snafu(b)
end
end
Foo.new.bar("test")
Foo.new.barbar("test")
puts Foo.get_comment(:snafu)
期望的输出:
Foo.bar was called with param: b = "test" test Foo.bar call finished, duration was 1ms test This really should be fixed
有没有办法实现这个目标?
答案 0 :(得分:11)
我有一个不同的方法:
class Object
def self.profile(method_name)
return_value = nil
time = Benchmark.measure do
return_value = yield
end
puts "#{method_name} finished in #{time.real}"
return_value
end
end
require "benchmark"
module Profiler
def method_added(name)
profile_method(name) if @method_profiled
super
end
def profile_method(method_name)
@method_profiled = nil
alias_method "unprofiled_#{method_name}", method_name
class_eval <<-ruby_eval
def #{method_name}(*args, &blk)
name = "\#{self.class}##{method_name}"
msg = "\#{name} was called with \#{args.inspect}"
msg << " and a block" if block_given?
puts msg
Object.profile(name) { unprofiled_#{method_name}(*args, &blk) }
end
ruby_eval
end
def profile
@method_profiled = true
end
end
module Comment
def method_added(name)
comment_method(name) if @method_commented
super
end
def comment_method(method_name)
comment = @method_commented
@method_commented = nil
alias_method "uncommented_#{method_name}", method_name
class_eval <<-ruby_eval
def #{method_name}(*args, &blk)
puts #{comment.inspect}
uncommented_#{method_name}(*args, &blk)
end
ruby_eval
end
def comment(text)
@method_commented = text
end
end
class Foo
extend Profiler
extend Comment
# This is the fake attribute, it profiles a single method.
profile
def bar(b)
puts b
end
def barbar(b)
puts(b)
end
comment("this really should be fixed")
def snafu(b)
end
end
关于这个解决方案的几点:
method_added
),而不需要别名。class_eval
而不是define_method
来定义新方法,以便能够支持带有块的方法。这也需要使用alias_method
。profile
或comment
时存储类实例变量,然后在添加方法时应用该变量。与前面的解决方案一样,method_added
挂钩用于跟踪添加新方法的时间,但不是每次都删除挂钩,而是钩子检查实例变量。应用AOP后将删除实例变量,因此它仅应用一次。如果多次使用同样的技术,可以进一步抽象。答案 1 :(得分:7)
好问题。这是我对实现的快速尝试(我没有尝试优化代码)。我冒昧地将profile
方法添加到了
Module
课程。通过这种方式,它将在每个类和模块定义中可用。它会更好
将其提取到模块中,并在需要时将其混合到类Module
中。
我也不知道是否要使profile
方法的行为与Ruby的public
/ protected
/ private
关键字相似,
但无论如何我都是这样实现的。调用profile
后定义的所有方法都会被分析,直到调用noprofile
。
class Module
def profile
require "benchmark"
@profiled_methods ||= []
class << self
# Save any original method_added callback.
alias_method :__unprofiling_method_added, :method_added
# Create new callback.
def method_added(method)
# Possible infinite loop if we do not check if we already replaced this method.
unless @profiled_methods.include?(method)
@profiled_methods << method
unbound_method = instance_method(method)
define_method(method) do |*args|
puts "#{self.class}##{method} was called with params #{args.join(", ")}"
bench = Benchmark.measure do
unbound_method.bind(self).call(*args)
end
puts "#{self.class}##{method} finished in %.5fs" % bench.real
end
# Call the original callback too.
__unprofiling_method_added(method)
end
end
end
end
def noprofile # What's the opposite of profile?
class << self
# Remove profiling callback and restore previous one.
alias_method :method_added, :__unprofiling_method_added
end
end
end
您现在可以按如下方式使用它:
class Foo
def self.method_added(method) # This still works.
puts "Method '#{method}' has been added to '#{self}'."
end
profile
def foo(arg1, arg2, arg3 = nil)
puts "> body of foo"
sleep 1
end
def bar(arg)
puts "> body of bar"
end
noprofile
def baz(arg)
puts "> body of baz"
end
end
按正常方式调用方法:
foo = Foo.new
foo.foo(1, 2, 3)
foo.bar(2)
foo.baz(3)
获得基准测试输出(以及原始method_added
回调的结果只是为了表明它仍然有效):
Method 'foo' has been added to 'Foo'.
Method 'bar' has been added to 'Foo'.
Method 'baz' has been added to 'Foo'.
Foo#foo was called with params 1, 2, 3
> body of foo
Foo#foo finished in 1.00018s
Foo#bar was called with params 2
> body of bar
Foo#bar finished in 0.00016s
> body of baz
需要注意的一点是,使用Ruby元编程动态获取参数名称是不可能的。 你必须解析原始的Ruby文件,这肯定是可能的,但有点复杂。查看parse_tree and ruby_parser 宝石了解详情。
有趣的改进是能够使用Module
类中的类方法定义此类行为。能够做类似的事情会很酷:
class Module
method_wrapper :profile do |*arguments|
# Do something before calling method.
yield *arguments # Call original method.
# Do something afterwards.
end
end
我将把这个元元编程练习再次留下。 : - )