Ruby 中 include、extend 和 prepend 的完整对比总结

0 阅读2分钟

概念

  • include:将模块的方法作为实例方法混入类,插入祖先链中(位于类自身之后)。
  • extend:将模块的方法作为**类方法(单例方法)**混入,混入的是单例类(eigenclass)。
  • prepend:将模块的方法作为实例方法混入,但插入到祖先链最前面,优先级最高(Ruby 2.0+)。

相同点

  • 三者都是 Ruby 的模块混入(Mixin)机制,用于代码复用。
  • 都会改变方法查找路径(Method Lookup Path)。

不同点

方面includeextendprepend
混入位置祖先链:类 → 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_listscope 等宏)。不污染实例;只影响类方法,不改 ancestors。
prepend用于增强或拦截原有方法(如日志、验证、性能监控、AOP),Rails 插件常用。优先覆盖;口诀:模块排最前面(可拦截 super)。