Ruby Ruby Hook Methods

taojay315 · 2016年01月14日 · 最后由 qinfanpeng 回复于 2016年01月14日 · 4246 次阅读

Hook Methods 一般译为钩子方法,可以理解为当触发了某个条件执行的方法。例如我们常见的 method_missing。

Method 相关

  • respond_to_missing?: 当尝试查看一个 missing 的方法时执行。
  • method_missing: 当调用一个不存在的方法时执行。
  • method_added: 当定义一个方法时执行
  • method_removed: 当一个方法被移除时执行
  • singleton_method_added: 当添加一个 singleton 方法时执行。
  • singleton_method_removed: 当一个 singleton 方法被移除时执行。
  • method_undefined: 当一个方法被 undefined 的时候执行,undef 和 remove 的区别在于当你在子类中你可以 undef 掉从父类继承而来的方法,而 remove 则不可以删除定义在父类中的方法。在父类中无论是使用 undef 和 remove 子类都无法继续使用这些方法。
  • singleton_method_undefined: 当一个 singleton 方法被 undefined 时刻执行。

示例代码:

# respond_to_missing?
class Base
  def respond_to_missing?(sym, *args)
    true
  end
end
Base.new.respond_to?(:missing_method)
# 这个跟重写respond_to?的区别主要在于使用下面这个方法
Base.new.method(:missing_method)
# => #<Method: Base#missing_method>

# method_missing
class Base
  def method_missing(method_name)
    puts method_name
  end
end
Base.new.missing_method
# => missing_method

# method_added

class Base
  def self.method_added(method_name)
    puts method_name
  end

  def missing_method
  end
end
# => missing_method
Base.class_eval { def missing_method2;end }
# => missing_method2

# method_removed
class Base
  def self.method_removed(method_name)
    puts method_name
  end
end    
Base.class_eval { remove_method :missing_method }
# => missing_method

# method_undefined
class Base
  def self.method_undefined(method_name)
    puts method_name
  end
end    
Base.class_eval { undef_method :missing_method }
# => missing_method

# singleton_method_added/removed/undefined
class Base
  def self.singleton_method_added(method_name)
    puts method_name
  end

  def self.missing_method
  end

  def Base.missing_method1
  end
end
def Base.missing_method2
end

# => singleton_method_added
# => missing_method
# => missing_method1
# => missing_method2

Class/Object相关

  • inherited: 当被继承时执行。
  • initialize_copy: 当调用 initialize_clone 和 initialize_dup 时执行。
  • initialize_dup: 当调用 dup,返回 dup 结果前时执行。
  • initialize_clone: 当调用 clone,进行 frozen 判断和返回 clone 结果之前执行。

介绍这几个方法之前我们先要了解一下 dup 和 clone 的区别,主要有以下两点:

  • dup 不会复制 frozen 状态,clone 会。
  • dup 不会复制 singleton class,clone 会。
# inherited
class Base
  def self.inherited(sub)
    puts sub.name
  end
end
class Sub < Base
end

# => Sub

# initialize_*
class Base
  def initialize_copy(other)
    puts 'initialize_copy'
    super
  end
  def initialize_dup(other)
    puts 'initialize_dup'
    super
  end
  def initialize_clone(other)
    puts 'initialize_clone'
    super
  end
end
Base.new.dup    
# => initialize_dup                  
# => initialize_copy
Base.new.clone                      
# => initialize_copy
# => initialize_clone

Modules 相关

  • included: 当 include 时执行。
  • append_features: 当 include 时执行。
  • prepended: 2.0 新增 prepend 的时候执行。
  • prepend_features:跟上面的 append_feature 可以一同理解。
  • extend_object: 当被 extend 时执行。
  • extended: 当被 extend 时执行。
  • const_missing: 当有常量丢失时执行。

首先来说,对于 include,prepend 这两者和 extend 的关系很容易区分。include 和 prepend 的区别在于对祖先链的修改方式,进一步影响方法的查找流程。

参考下面的例子:

module M
  def hello
    puts 'Hello from M'
    super
  end
end

module N
  def hello
    puts 'Hello from N'
  end
end

class C
  include N
  prepend M
  def hello
    puts 'hello from C'
    super
  end
end
C.new.hello
#=> Hello from M
#=> hello from C
#=> Hello from N

其中 included 和 append_features 是一组,prepended 和 prepend_features 是一组,extended 和 extend_object 是一组。

以 included 和 append_features 来说,append_features 这个方法是真正把 module 里的代码混入到 include 此 module 的地方,而 included 仅仅是在完成混入之后执行的一个回调,下面是一个伪示例:

module M
  def self.append_features(mod)
    mod.class_eval {def new_method;puts 'say hi!';end}
  end

  def self.included(mod)
    puts 'included'
    mod.new.new_method
  end

  def self.const_missing(const_name) # 当常量丢失时执行
    puts const_name
  end

  MissingConst # 访问不存在常量
end
# => MissingConst
M::MissingPerson
# => MissingPerson # 访问不存在常量

对于 prepend 和 extend 两组来说也是如此。

Marshal 相关

  • marshal_dump: 当 Marshal.dump(@obj) 时执行。
  • marshal_load: 当 Marshal.load(@obj) 时执行。
class C
  attr_accessor :arrs, :d_arrs
  def marshal_dump
    Marshal.dump(arrs)
  end

  def marshal_load(data)
    self.d_attrs = Marshal.load(data)
  end
end

k = C.new
k.arrs = [1,2,3,4,5]
k2 = Marshal.load(Marshal.dump(k))
k2.d_arrs 
# => [1,2,3,4,5]
k2.arrs 
# => nil

Coercion 相关

  • coerce: 当使用一个二元操作符的时候会在第二个参数上触发,当我们重载运算符的时候很有用。
  • to_*系列方法:这些方法虽然看起来不像,但其实很多时候都会被触发:
# coerce
class C
  def coerce(n)
    puts n
    [n,5]
  end
  def to_s
    "CCCC"
  end
  def to_int
    3
  end
  def to_proc
    Proc.new {|i| puts i}
  end
end
3*C.new
#=> 3
#=> 15
"#{C.new}" #=> CCCC 会触发to_s
[1,2,3,4,5][C.new]                                               
# => 4 触发to_int
[1,2,3,4,5].each(&C.new) # to_proc
# => 1
# => 2
# => 3
# => 4
# => 5

:plus1: 。啰嗦一点:to_* 系列方法又有为“隐式”和”显示“之说,比如to_strto_int是”隐式类型转换方法“是 Ruby 自动调用的; to_sto_i是”显示类型转换方法“,一般用于开发人员自己手动调用。不难发现 Ruby 很人性,需开发人员自己显示调用的,都是更精简(to_i <--> to_int)的。当然字符串混入是个例外。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号