使用元编程将方法包围在 class
use metaprogramming to surround methods in a class
我有 classes 方法在进入和退出方法时进行记录,如下所示:
<br>
def 方法名 1(args)
@logger.debug(">>#{<strong>被叫</strong>}")
...
@logger.debug("<<#{<strong>被叫</strong>}")
结束</p>
<p>def methodName2(args)
@logger.debug(">>#{<strong>被叫</strong>}")
...
@logger.debug("<<#{<strong>被叫</strong>}")
结尾
我想知道是否有一种元编程方式可以用记录器调用包围这些方法?
这将涉及识别 class 中我想要首先包围然后包围它们的所有方法。
一个
您可以使用周围别名。 Alias 原来的方法,然后用额外的代码重新定义它:
alias_method :bare_methodname1, :methodname1
def methodname1(*args)
@logger.debug(">>#{callee}")
result = bare_methodname1(*args)
@logger.debug("<<#{callee}")
result
end
这与您现在得到的并没有太大不同,但是当您将它与一组方法名称组合时,您会得到更多您想要的:
method_names_ary.each do |name|
alias_method "bare_" + name, name
define_method(name) do |*args|
@logger.debug(">>#{callee}")
result = send("bare_" + name, *args)
@logger.debug("<<#{callee}")
result
end
end
将它放在任何方法之外的目标 class 中,它应该重新定义数组中的所有方法以获得您想要的额外代码。
我想这个解决方案应该有所帮助:
class Foo
def initialize
(self.methods - Object.methods).each do |method|
# we need to make alias for method
self.class.send(:alias_method, "#{method}_without_callback", method)
# save method params, and destroy old method
params = self.method(method).parameters.map(&:last).join(',')
self.class.send(:undef_method, method)
# creating new method with old name, and pass params to this
eval("
self.class.send(:define_method, method) do |#{params}|
puts 'Call_before'
self.send('#{method}_without_callback', #{params})
puts 'Call_after'
end
")
end
end
def say_without_param
puts "Hello!"
end
def say_hi(par1)
puts "Hi, #{par1}"
end
def say_good_bye(par1, par2)
puts "Good bye, #{par1} #{par2}"
end
end
所以当我们创建一个对象时,初始化后会创建别名方法,销毁旧方法,并创建带有call_backs的新方法;
用法示例:
obj = Foo.new
obj.say_without_param # => Call_before
Hello!
Call_after
obj.say_hi('Johny') # => Call_before
Hi, Johny
Call_after
obj.say_good_bye('mr.', 'Smith') => Call_before
Good bye, mr. Smith
Call_after
我倾向于在 class 前面添加一个动态创建的匿名模块,其实例方法使用 super
调用同名 class 的实例方法,在打印方法进入消息之后和打印方法退出消息之前。
让我们从创建一个 class 开始,它有两个实例方法,一个在调用时传递一个块。
class C
def mouse(nbr_mice, impairment)
puts "%d %s mice" % [nbr_mice, impairment]
end
def hubbard(*args)
puts yield args
end
end
C.ancestors
#=> [C, Object, Kernel, BasicObject]
c = C.new
c.mouse(3, 'blind')
# 3 blind mice
c.hubbard('old', 'mother', 'hubbard') { |a| a.map(&:upcase).join(' ') }
# OLD MOTHER HUBBARD
现在我们构建一个方法来创建匿名模块并将其添加到 class。
def loggem(klass, *methods_to_log)
log_mod = Module.new do
code = methods_to_log.each_with_object('') { |m,str| str <<
"def #{m}(*args); puts \"entering #{m}\"; super; puts \"leaving #{m}\"; end\n" }
class_eval code
end
klass.prepend(log_mod)
end
我们现在已准备好调用此方法,参数等于模块要添加到的 class 和要记录的 class 的实例方法。
loggem(C, :mouse, :hubbard)
C.ancestors
#=> [#<Module:0x007fedab9ccf48>, C, Object, Kernel, BasicObject]
c = C.new
c.method(:mouse).owner
#=> #<Module:0x007fedab9ccf48>
c.method(:mouse).super_method
#=> #<Method: Object(C)#mouse>
c.method(:hubbard).owner
#=> #<Module:0x007fedab9ccf48>
c.method(:hubbard).super_method
#=> #<Method: Object(C)#hubbard>
c.mouse(3, 'blind')
# entering mouse
# 3 blind mice
# leaving mouse
c.hubbard('old', 'mother', 'hubbard') { |a| a.map(&:upcase).join(' ') }
# entering hubbard
# OLD MOTHER HUBBARD
#leaving hubbard
参见 Module::new and Module#prepend。
您可以创建一个类似于 def
的 class 方法来为您添加观察者。这会稍微改变方法定义语法,但可能会使代码更具可读性。
即
module MethodLogging
def log_def(method_name, &definition)
define_method(method_name) do |*args|
@logger.debug(">>#{__callee__}")
definition.call(*args)
@logger.debug("<<#{__callee__}")
end
end
end
class MyClass
extend MethodLogging
def initialize
# make sure class has @logger defined, or else include it in some way in the MethodLogging module
@logger = Logger.new(STDOUT)
end
def regular_method(x)
puts x
end
log_def :logged_method do |x|
puts x
end
end
instance = MyClass.new
instance.regular_method(3)
# hello
instance.logged_method(3)
# D, [2017-03-22T14:59:18.889285 #58206] DEBUG -- : >>logged_method
# world
# D, [2017-03-22T14:59:18.889440 #58206] DEBUG -- : <<logged_method
除了新的方法定义语法之外,还有一个小缺点,如果您不尊重方法的数量,就会出现奇怪的行为。使用此方法 instance.logged_method()
和 instance.logged_method('hello', 'world')
都不会引发错误。
我有 classes 方法在进入和退出方法时进行记录,如下所示:
<br>
def 方法名 1(args)
@logger.debug(">>#{<strong>被叫</strong>}")
...
@logger.debug("<<#{<strong>被叫</strong>}")
结束</p>
<p>def methodName2(args)
@logger.debug(">>#{<strong>被叫</strong>}")
...
@logger.debug("<<#{<strong>被叫</strong>}")
结尾
我想知道是否有一种元编程方式可以用记录器调用包围这些方法? 这将涉及识别 class 中我想要首先包围然后包围它们的所有方法。
一个
您可以使用周围别名。 Alias 原来的方法,然后用额外的代码重新定义它:
alias_method :bare_methodname1, :methodname1
def methodname1(*args)
@logger.debug(">>#{callee}")
result = bare_methodname1(*args)
@logger.debug("<<#{callee}")
result
end
这与您现在得到的并没有太大不同,但是当您将它与一组方法名称组合时,您会得到更多您想要的:
method_names_ary.each do |name|
alias_method "bare_" + name, name
define_method(name) do |*args|
@logger.debug(">>#{callee}")
result = send("bare_" + name, *args)
@logger.debug("<<#{callee}")
result
end
end
将它放在任何方法之外的目标 class 中,它应该重新定义数组中的所有方法以获得您想要的额外代码。
我想这个解决方案应该有所帮助:
class Foo
def initialize
(self.methods - Object.methods).each do |method|
# we need to make alias for method
self.class.send(:alias_method, "#{method}_without_callback", method)
# save method params, and destroy old method
params = self.method(method).parameters.map(&:last).join(',')
self.class.send(:undef_method, method)
# creating new method with old name, and pass params to this
eval("
self.class.send(:define_method, method) do |#{params}|
puts 'Call_before'
self.send('#{method}_without_callback', #{params})
puts 'Call_after'
end
")
end
end
def say_without_param
puts "Hello!"
end
def say_hi(par1)
puts "Hi, #{par1}"
end
def say_good_bye(par1, par2)
puts "Good bye, #{par1} #{par2}"
end
end
所以当我们创建一个对象时,初始化后会创建别名方法,销毁旧方法,并创建带有call_backs的新方法;
用法示例:
obj = Foo.new
obj.say_without_param # => Call_before
Hello!
Call_after
obj.say_hi('Johny') # => Call_before
Hi, Johny
Call_after
obj.say_good_bye('mr.', 'Smith') => Call_before
Good bye, mr. Smith
Call_after
我倾向于在 class 前面添加一个动态创建的匿名模块,其实例方法使用 super
调用同名 class 的实例方法,在打印方法进入消息之后和打印方法退出消息之前。
让我们从创建一个 class 开始,它有两个实例方法,一个在调用时传递一个块。
class C
def mouse(nbr_mice, impairment)
puts "%d %s mice" % [nbr_mice, impairment]
end
def hubbard(*args)
puts yield args
end
end
C.ancestors
#=> [C, Object, Kernel, BasicObject]
c = C.new
c.mouse(3, 'blind')
# 3 blind mice
c.hubbard('old', 'mother', 'hubbard') { |a| a.map(&:upcase).join(' ') }
# OLD MOTHER HUBBARD
现在我们构建一个方法来创建匿名模块并将其添加到 class。
def loggem(klass, *methods_to_log)
log_mod = Module.new do
code = methods_to_log.each_with_object('') { |m,str| str <<
"def #{m}(*args); puts \"entering #{m}\"; super; puts \"leaving #{m}\"; end\n" }
class_eval code
end
klass.prepend(log_mod)
end
我们现在已准备好调用此方法,参数等于模块要添加到的 class 和要记录的 class 的实例方法。
loggem(C, :mouse, :hubbard)
C.ancestors
#=> [#<Module:0x007fedab9ccf48>, C, Object, Kernel, BasicObject]
c = C.new
c.method(:mouse).owner
#=> #<Module:0x007fedab9ccf48>
c.method(:mouse).super_method
#=> #<Method: Object(C)#mouse>
c.method(:hubbard).owner
#=> #<Module:0x007fedab9ccf48>
c.method(:hubbard).super_method
#=> #<Method: Object(C)#hubbard>
c.mouse(3, 'blind')
# entering mouse
# 3 blind mice
# leaving mouse
c.hubbard('old', 'mother', 'hubbard') { |a| a.map(&:upcase).join(' ') }
# entering hubbard
# OLD MOTHER HUBBARD
#leaving hubbard
参见 Module::new and Module#prepend。
您可以创建一个类似于 def
的 class 方法来为您添加观察者。这会稍微改变方法定义语法,但可能会使代码更具可读性。
即
module MethodLogging
def log_def(method_name, &definition)
define_method(method_name) do |*args|
@logger.debug(">>#{__callee__}")
definition.call(*args)
@logger.debug("<<#{__callee__}")
end
end
end
class MyClass
extend MethodLogging
def initialize
# make sure class has @logger defined, or else include it in some way in the MethodLogging module
@logger = Logger.new(STDOUT)
end
def regular_method(x)
puts x
end
log_def :logged_method do |x|
puts x
end
end
instance = MyClass.new
instance.regular_method(3)
# hello
instance.logged_method(3)
# D, [2017-03-22T14:59:18.889285 #58206] DEBUG -- : >>logged_method
# world
# D, [2017-03-22T14:59:18.889440 #58206] DEBUG -- : <<logged_method
除了新的方法定义语法之外,还有一个小缺点,如果您不尊重方法的数量,就会出现奇怪的行为。使用此方法 instance.logged_method()
和 instance.logged_method('hello', 'world')
都不会引发错误。