Ruby 关于 included 方法的问题

runup · 2016年05月31日 · 最后由 runup 回复于 2016年06月01日 · 4421 次阅读

代码如下:

module ModuleDemo
  def self.included(mod)
    puts "#{self} is included in #{mod}"
  end

  def self.append_features(mod)
    puts "#{mod} append_features by #{self}"
  end
end

class ClassDemo
  include ModuleDemo
end

代码输出 #
ClassDemo append_features by ModuleDemo
ModuleDemo is included in ClassDemo

问题 1:included 是 hook methods,也是 Module 的私有实例方法,按照元编程的思想,私有实例变量不能被显示调用,是否与这里的 self.included 相悖。 问题 2:hook methods 方法被重写的时候,一般都是 self.included 形式,这样子有什么讲究?为什么不能是如下的形式:

module ModuleDemo
  #当然include的时候是无效的
  def included(mod)
    puts "puts something"
  end
end

问题 3:included 方法在 api 中文档说明为:Callback invoked whenever the receiver is included in another module or class. This should be used in preference to Module.append_features. if your code wants to perform some action when a module is included in another. 其中英文 in preference to 应该是优先于的意思,我的理解是 included 优先于 append_features 执行,但是如上代码的执行情况,刚好相反,哪里出了问题? 问题 4:api 文档中只是给出了 included 方法作为 hook methods 被重写的情形,但是对于直接使用 included 未提及。比如下面的这段代码,included 的作用是什么?,included 的一般用法是什么?

module HairColors 
  extend ActiveSupport::Concern 
  included do 
    mattr_accessor :hair_colors 
  end 
end 

class Person 
  include HairColors 
end 

Person.hair_colors = [:brown, :black, :blonde, :red] 
Person.hair_colors # => [:brown, :black, :blonde, :red] 
Person.new.hair_colors # => [:brown, :black, :blonde, :red] 
HairColors.hair_colors # => undefined method `hair_colors' for HairColors:Module

#1

def self.included(mod)
  puts "#{self} is included in #{mod}"
end

是定义方法,不是调用,调用的逻辑并不在这里

看到了一种 included 的用法

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

  module ClassMethods
    def bar
      puts 'class method'
    end
  end

  def foo
    puts 'instance method'
  end
end

class Baz
  include Foo
end

Baz.bar # class method
Baz.new.foo # instance method
Baz.foo # NoMethodError: undefined method ‘foo’ for Baz:Class
Baz.new.bar # NoMethodError: undefined method ‘bar’ for #<Baz:0x1e3d4>

#2

module ModuleDemo
  def included(mod)
  end
  def self.included(mod)
  end
end

这两个是不同的函数,included 是 ModuleDemo 的实例方法,self.included 是 ModuleDemo 单件类的实例方法

hook methods 的本质是在某事件发生后调用特定的方法,在 include 事件中这个特定的方法就是模块单件类的实例方法 included

为什么是这个特定的方法呢? 因为 included 作为 ModuleDemo 的实例方法,在类被实例化之前是无法被调用的

#4 hook methods 一般都不会主动去调用

included do 
  mattr_accessor :hair_colors 
end 

这里的 included 并不是前面的 included 他是在 ActiveSupport::Concern 里定义的新的方法

#3 文档中的 in preference to 并不是在什么之前的意思,而是说: Module.append_features 和 included 都能实现同样的功能,但你应该优先考虑使用 included

#2 楼 @rikiwong 这个和正文中提到的 included 用法一致,是在被方法重写,而不是方法调用

#1 楼 @kikyous 解决了第三个问题。第一个问题在抛出来的时候,有想过这里是进行方法定义,因此可以使用 self.included 进行方法定义,只是想为什么不能直接定义 def include;end 方法,经过你的答疑,我的理解是 module 没有 new 方法,不能进行实例化,因此 def include;end 方法不能被执行。def self.included;end 方法在模块被 include 的时候,作为模块的单件类方法,被触发实现。至于第四个问题,我再研究下源码,感谢指点,其中 hook methods 的解析加深我对这个方法的理解。

#7 楼 @runup "我的理解是 module 没有 new 方法,不能进行实例化,因此 def included;end 方法不能被执行。" 这个跟能不能实例化没关系,即使把这个 module mixin 到一个 class 中再 new 出一个实例也没有意义。 一个实例如何被 included 呢?被 included 的只可能是 module 吧。

问题 1

不是很理解你的意思,不过我觉得不矛盾啊

使用 Mixin 的时候实际上只是把 module 插入该类的 ancestors 里,每次 include 都不会产生新的实例对象,hook_methods 必然只能写在 singleton_class 里。比如一些 class 用的 hook_method 也一样定义在单例类里

class Foo
  def self.inherited(subclass)
    puts "New subclass: #{subclass}"
  end
end

class Bar < Foo
end

class Baz < Bar
end

问题 2

当然不能是这种形式的,self.included 是 Ruby 定义的 hook_method

http://ruby-doc.org/core-2.3.1/Module.html#method-i-included

而你上面定义的只是一个 instance_method,用来附加到其他类上的一个普通方法

问题 3 & 问题 4

为什么要用 included,在 api 文档里写得很清楚,主要是为了弥补 included 方法在嵌套使用 Mixin 时的一个缺点

module Bar
  include Foo
  def self.included(base)
    base.method_injected_by_foo
  end
end

class Host
  include Bar
end

上面的代码的目的是在 include Bar 时调用 Host.method_injected_by_foo 方法,但是因为 Bar 里 Mixin 了 Foo,导致 base 变成了 Foo 而不是 Host,用 included 改写可以避免这个问题

require 'active_support/concern'

module Foo
  extend ActiveSupport::Concern
  included do
    def self.method_injected_by_foo
      ...
    end
  end
end

module Bar
  extend ActiveSupport::Concern
  include Foo

  included do
    self.method_injected_by_foo
  end
end

class Host
  include Bar # It works, now Bar takes care of its dependencies
end

应用场景

https://github.com/huacnlee/rails-settings-cached/blob/master/lib/rails-settings/extend.rb

#9 楼 @adamshen 针对问题 1,def included;end 必须生成实例化对象才能被调用,def self.included;end 类对象可以直接调用,因此回调方法只能写成单例形式,这么理解是否合适?

#10 楼 @runup 感觉你想得太多,反而把自己绕晕了。

#10 楼 @runup 其实你知道 hook_method 是用 self.included 定义的就可以了,没有必要再绕进去。假设最初 ruby 在设计 api 的时候把 included 写成实例方法,再到执行时变更部分流程来支持这个定义,这样也不是不可能的。

感觉你学习的方法是试图把所有问题都用自己现有的知识体系来解释一遍,在整个逻辑推演上非要完全吃透没有任何盲点,我觉得这样好累。个人我觉得我们的知识结构能够超越现有的工作场景一点点就可以了,之后的再根据实际的需求再不断学习,否则每个单领域上损耗的精力未必能够产生实际效益啊。

#12 楼 @adamshen #11 楼 @qinfanpeng 谢谢两位,觉得目前正处于这个阶段吧,感谢指点。

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