[关闭]
@elibinary 2017-06-11T05:15:02.000000Z 字数 1293 阅读 780

[读书笔记] Ruby 的 Module 扩展

Ruby


上篇中有简单说到 module 的 include,下面来看一些稍复杂的使用情况以及 ruby 是如何处理的

include

那么,首先问题:在 module A 中 include 了 module B,然后 class M 再 include 模块 A,现在 class M 的祖先链是什么样子的?

  1. module B
  2. def say
  3. puts 'I am B'
  4. end
  5. end
  6. module A
  7. include B
  8. def say
  9. puts "I'm A"
  10. end
  11. end
  12. class M
  13. include A
  14. end
  15. M.new.say
  16. # => I'm A

可以看出方法查找先找到的 A 中的 #say 方法,那么 M 的祖先链应该是这样的:

  1. M.ancestors
  2. # => [M, A, B, Object, Kernel, BasicObject]

事实上,当把 B include 到 A 中时, Ruby 会创建 B 的副本,然后把它设置为 A 的超类,也就是 A 的 super 指针指向 B 的副本。
虽然 module 本身不允许被指派超类,然后在 Ruby 内部可以。
最后把 A include 到 M 中,Ruby 会迭代这两个模块,然后依次作为超类插入。

修改已经被 include 的 module 会怎样?

  1. module B
  2. def say
  3. puts 'I am B'
  4. end
  5. end
  6. class M
  7. include B
  8. end
  9. M.new.say
  10. # I am B
  11. M.new.methods
  12. # => => [:say, :instance_of?, :public_send ...
  13. module B
  14. def re_say
  15. puts 'Double B'
  16. end
  17. end
  18. M.new.say
  19. # I am B
  20. M.new.re_say
  21. # Double B
  22. M.new.methods
  23. # => [:say, :re_say, :instance_of?, :public_send ...

那么,这是为什么呢,Ruby 做了什么处理

事实上,Ruby 在 include module 时,拷贝副本时拷贝的是 RClass 结构体,但是并不连 m_tbl 指向的方法表一同拷贝,也就是说副本与原 module 是共享同一个方法表的。

### prepend

有时候我们会有需求期望当前类的方法不会重载掉 module 中的方法,这个时候就会使用 #prepend 方法

  1. module B
  2. def say
  3. puts 'I am B'
  4. end
  5. end
  6. class M
  7. prepend B
  8. def say
  9. puts 'I am M'
  10. end
  11. end
  12. M.new.say
  13. # I am B
  14. M.ancestors
  15. # => [B, M, Object, Kernel, BasicObject]

使用 prepend 时,在超类链中,Ruby 会把 B 放在 M 前面

那么, Ruby 在其中做了什么呢?Ruby 是沿祖先链向后查找方法的,它是如何在 super 指针向下指的情况下做到这样的?

实际上,Ruby 在内部使用了个巧妙的小手段,当使用 prepend 包含一个 module 时,Ruby 会创建 M 的 origin class,并把它设置为前置模块 B 的超类。Ruby 使用 rb_classext_struct 机构体中的 origin 指针指向原 class。 Ruby 还会从 origin class 中把方法全部移动到原 class 中,这意味着这些方法将是可以被前置模块 B 重载的。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注