使用元编程将方法包围在 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') 都不会引发错误。