使用元编程来包围类中的方法

我有类的方法,这些方法在输入和退出方法时记录,如下所示:
def methodName1(args) @logger.debug(">>#{ callee }") ... @logger.debug("<<#{ callee }") end def methodName2(args) @logger.debug(">>#{ callee }") ... @logger.debug("<<#{ callee }") end


def methodName1(args) @logger.debug(">>#{ callee }") ... @logger.debug("<<#{ callee }") end def methodName2(args) @logger.debug(">>#{ callee }") ... @logger.debug("<<#{ callee }") end

我想知道是否有一种元编程方式用记录器调用来包围方法? 它将涉及识别我想要先包围然后围绕它们的类中的所有方法。

一个

我想这个解决方案应该有所帮助

 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 

我倾向于在类之前添加一个动态创建的匿名模块,其实例方法在打印方法条目消息之后和打印方法退出消息之前使用super来调用同名类的实例方法。

让我们从创建一个带有两个实例方法的类开始,一个在被转移时传递一个块。

 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 

现在我们构造一个方法来创建匿名模块并将其预先添加到类中。

 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 

我们现在准备调用此方法,其参数等于要添加模块的类以及要记录的该类的实例方法。

 loggem(C, :mouse, :hubbard) C.ancestors #=> [#, C, Object, Kernel, BasicObject] c = C.new c.method(:mouse).owner #=> # c.method(:mouse).super_method #=> # c.method(:hubbard).owner #=> # c.method(:hubbard).super_method #=> # 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和Module #prepend 。

你可以使用周围的别名。 别名原始方法,然后使用额外代码重新定义它:

 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 

将它放在任何方法之外的目标类中,它应该重新定义数组中的所有方法以获得所需的额外代码。

您可以创建一个类似于def的类方法,为您添加观察者。 这会稍微改变方法定义语法,但可能会使代码更易读。

 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 -- : < 

除了新的方法定义语法之外,还有一个小的缺点,如果你不尊重方法的arity,你会得到奇怪的行为。 instance.logged_method()instance.logged_method('hello', 'world')都不会引发此方法的错误。