Ruby 中的方法拦截

使用 alias_method

如果想调整 实例方法 的行为,例如在执行 to_i 方法之前验证字符串的格式,可以这样:

class String
  def to_i_with_validation
    raise "Invalid integer format: #{self.inspect}" if self !~ /\A-?\d+\Z/
    to_i_without_validation
  end

  alias_method :to_i_without_validation, :to_i
  alias_method :to_i, :to_i_with_validation
end

这样 "abc".to_i 就会抛出异常。

对于 类方法,例如在调用 Time.now 方法时打印日志,可以这样:

class << Time
  def now_with_logging
    puts '--- calling now'
    result = now_without_logging
    puts "--- result: #{result}"
    puts '--- done'
    result
  end

  alias_method :now_without_logging, :now
  alias_method :now, :now_with_logging
end

如此通过 alias_method 就可以实现 before, after, around 类型的拦截器。

如果是在 Rails 项目中使用这种方式,Rails 提供了简洁的封装方法:alias_method_chain

使用 super

例如对于这个 Robot 类:

class Robot
  module Base
    def self.included(base)
      base.extend(ClassMethods)
    end

    def initialize(name)
      @name = name
    end

    def say_hello
      puts "Hello! My name is #{@name}."
    end

    module ClassMethods
      def greetings
        puts 'Hi, we are robots!'
      end
    end
  end

  include Base # 注意这里
end

可以使用 super 的方式来调整类的类方法和实例方法的行为:

module LogRobot
  def self.included(base)
    base.extend(ClassMethods)
  end

  def say_hello
    puts "--- calling say_hello with name: #{@name}"
    super
  end

  module ClassMethods
    def greetings
      puts '--- calling greetings'
      super
    end
  end
end

Robot.send(:include, LogRobot)

执行:

Robot.greetings
Robot.new('Marvin').say_hello

结果:

--- calling greetings

Hi, we are robots!

--- calling say_hello with name: Marvin

Hello! My name is Marvin.

使用 super 这种方式的优点是代码更加清晰,缺点是此方式只适用于原始的类中使用了类似于 include Base 定义方法的情况。所以对于自己编写的代码,推荐使用这种方法;而对于其他的例如第三方库,只有用 alias_method 的方式了。

P.S. Rails 提供了封装 module 代码的更简洁的方法:ActiveSupport::Concern

P.P.S 对于更复杂的 AOP 编程需求,可以使用或参考专门的库实现, 如 aspectorAquarium

2013-10-21 00:00184rubyaop