概念
- include:将模块的方法作为实例方法混入类,插入祖先链中(位于类自身之后)。
- extend:将模块的方法作为**类方法(单例方法)**混入,混入的是单例类(eigenclass)。
- prepend:将模块的方法作为实例方法混入,但插入到祖先链最前面,优先级最高(Ruby 2.0+)。
相同点
- 三者都是 Ruby 的模块混入(Mixin)机制,用于代码复用。
- 都会改变方法查找路径(Method Lookup Path)。
不同点
| 方面 | include | extend | prepend |
|---|---|---|---|
| 混入位置 | 祖先链:类 → Module | 单例类(eigenclass) | 祖先链最前面:Module → 类 |
| 方法类型 | 实例方法 | 类方法 / 单例方法 | 实例方法 |
| 对 ancestors 的影响 | 在类之后 | 无影响 | 在类之前 |
| 方法优先级 | 类方法 > 模块方法 | 类方法直接添加 | 模块方法 > 类方法 |
使用场景示例 + ancestors 演示
1. include 示例
module Greeting
def hello
"Hello from Greeting"
end
end
class Person
include Greeting
end
puts Person.ancestors
# 输出结果:
# [Person, Greeting, Object, Kernel, BasicObject]
说明:Greeting 出现在 Person 之后。
2. extend 示例
module ClassMethods
def hello
"Hello from ClassMethods"
end
end
class Person
extend ClassMethods
end
puts Person.ancestors
# 输出结果:
# [Person, Object, Kernel, BasicObject]
puts Person.singleton_class.ancestors
# 输出结果:
# [#<Class:Person>, ClassMethods, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
说明:extend 不会影响 Person.ancestors,而是影响单例类的祖先链。
3. prepend 示例(最重要)
module Logging
def hello
puts "Logging start..."
super # 调用原类的 hello 方法
puts "Logging end..."
end
end
class Person
prepend Logging
def hello
"Hello from Person"
end
end
puts Person.ancestors
# 输出结果:
# [Logging, Person, Object, Kernel, BasicObject]
说明:Logging 排在 Person 之前,因此 Logging#hello 会优先被调用。
总结建议
| 机制 | 适用场景 | 优势与记忆口诀 |
|---|---|---|
| include | 最常用,用于共享实例方法(如 Rails Concern 中的模型共享逻辑)。 | 简单复用;模块排后面(类优先)。 |
| extend | 用于添加类方法(如 acts_as_list、scope 等宏)。 | 不污染实例;只影响类方法,不改 ancestors。 |
| prepend | 用于增强或拦截原有方法(如日志、验证、性能监控、AOP),Rails 插件常用。 | 优先覆盖;口诀:模块排最前面(可拦截 super)。 |