Ruby 模块(Module)与 Mixin 详解:include、extend、prepend 的区别
模块是 Ruby 组织代码与实现"多重继承"的核心机制。本文讲清模块的命名空间与 Mixin 两大用途,并用实例对比 include、extend、prepend 三种混入方式的方法查找顺序。
Ruby 模块有两大用途:当命名空间(Admin::User)组织代码、当 Mixin 在类间共享行为。三种混入方式的区别在于方法查找链中的位置:include 插入类的后面(实例方法)、extend 加到类自身(类方法)、prepend 插到类的前面(可覆盖并 super 回调)。
模块是什么
模块和类一样是方法与常量的容器,但不能实例化。这个限制换来两大能力:组织命名空间和跨类复用代码。
module Payments
class CreditCard; end
class Alipay; end
end
Payments::CreditCard.new # 命名空间用法
Rails 项目里 app/models/admin/user.rb 对应 Admin::User,模块就是避免类名冲突的隔离墙。
Mixin:Ruby 的"多重继承"
Ruby 类只能单继承,但行为复用不受限——把公共行为写进模块,需要的类 include 进来:
module Taggable
def tags
@tags ||= []
end
def add_tag(tag)
tags << tag unless tags.include?(tag)
end
end
class Article
include Taggable
end
class Product
include Taggable
end
Article 和 Product 毫无继承关系,却共享了同一套标签行为。Ruby 标准库的 Enumerable、Comparable 都是这个模式的典范。
include vs extend vs prepend
三者的区别在于模块进入方法查找链的位置:
module Greeter
def hello
'来自模块的问候'
end
end
class A
include Greeter # 实例方法:A.new.hello 可用
end
class B
extend Greeter # 类方法:B.hello 可用
end
class C
prepend Greeter # 实例方法,但模块排在类"前面"
def hello
'来自类的问候'
end
end
C.new.hello # => '来自模块的问候'(prepend 的模块优先)
- include:模块插在类和父类之间 →
A.ancestors为[A, Greeter, Object, ...]; - extend:模块插到类的单例类上 → 方法变成类方法;
- prepend:模块插在类之前 → 模块方法覆盖类方法,且模块内可以
super调回类的实现。
prepend 的独特价值:无痕增强
prepend + super 是实现"环绕增强"的干净方式,不需要 alias_method 链:
module Logging
def save
puts "保存前记录日志"
super
end
end
class Document
prepend Logging
def save
puts '真正的保存'
end
end
Document.new.save
# => 保存前记录日志
# => 真正的保存
Rails 的不少 instrumentation 和第三方 gem 的猴补丁都用这个机制——比直接改写原方法安全,因为原始实现通过 super 依然可达。
模块里的常量与方法可见性
模块可以嵌套定义常量(module Api; TIMEOUT = 5; end,用 Api::TIMEOUT 访问)。模块内的 private/protected 与类中语义一致;想让模块方法"既能被 include 又能直接以模块名调用",用 module_function(典型例子:Math.sqrt 与 include Math 后直接用 sqrt)。
常见问题(FAQ)
Q:什么场景用模块而不是继承?
行为是"能力"而非"身份"时用模块:一个类"是"什么用继承表达(Dog < Animal),"能"什么用 Mixin 表达(Taggable、Searchable、Cacheable)。类之间有 is-a 关系才继承,否则优先组合与 Mixin。
Q:include 多个模块方法名冲突怎么办?
后 include 的模块在查找链中更靠前,覆盖先 include 的同名方法。显式顺序:include A; include B 时 B 优先。依赖顺序的冲突是设计坏味道,最好改名或用 prepend 明确叠加关系。
Q:Concern(ActiveSupport::Concern)和直接 include 有什么区别?
Concern 解决了两个问题:依赖模块的包含顺序自动化(included 块里的 has_many 等类级调用按正确时机执行)、ClassMethods 子模块自动 extend。Rails 项目写可复用行为时优先用 Concern,纯 Ruby 项目普通模块即可。
参考文档
本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。