Rails extract_options! 实现的疑问

wwwicbd · 2018年06月16日 · 最后由 wwwicbd 回复于 2018年06月18日 · 1286 次阅读

active_support/core_ext/array/extract_options.rb

class Hash
  def extractable_options?
    instance_of?(Hash)
  end
end

class Array
  def extract_options!
    if last.is_a?(Hash) && last.extractable_options?
      pop
    else
      {}
    end
  end
end

疑问:

extract_options! 中为啥要先判断 is_a? 再判断 instance_of? 呢? 我的理解是后者约束条件更强,没必要使用 is_a? 方法吧。

class H < Hash;
end

h = H.new(:hello => :world)

p h.is_a? H
p h.is_a? Hash
p h.instance_of? H
p h.instance_of? Hash

=begin
true
true
true
false
=end

我觉得就得这样写才对呀

你不需要非得把 Array#extract_options!Hash#extractable_options? 放在一起来讨论,Hash#extractable_options? 也可能在其他场景下被调用,它具体做什么对调用者而言是不 care 的,可能很简单也可以很复杂,只是这里碰巧只有一句 instance_of?(Hash)

所以你问 Array#extract_options! 为什么不直接使用 instance_of?(Hash)?因为对于 Array 来说,它不需要关心 Hash#extractable_options? 到底做了什么判断,于它而言这只是一支 API,能返回它想要的结果就行了。万一有一天 Hash#extractable_options? 的代码发生了变动,它自己也不需要做任何改动

相反,如果使用 instance_of?(Hash),那反而为以后多埋下了一个陷阱。请时刻记得行为守则 —— DRY

看到这个 commit,在回头看extractable_options?上方的注释,其实就是为了可以自定义子类,就像你例子中的 H,如果

class H < Hash
  def extractable_options?
    false
  end
end

那么

hash = H.new({foo: 1})
array = [hash]
options = array.extract_options! # 这边是取不出 hash 的
需要 登录 后方可回复, 如果你还没有账号请 注册新账号