Ruby 模块
模块(Module)是 Ruby 解决"单继承局限"的钥匙。它有两个核心用途:命名空间(组织代码、避免命名冲突)和 mixin(把一组方法"塞"进类里,实现类似多继承的能力)。Ruby 标准库里 Comparable、Enumerable 都是模块,会用它们能让你的类瞬间拥有几十个方法。
1. 模块作为命名空间
当代码变大、有多个 User VERSION 这类通用名字时,把它们包进模块,用 :: 访问:
# 模块作为命名空间:用 :: 访问内部
module MyApp
VERSION = "1.0.0"
class User
def initialize(name); @name = name; end
attr_reader :name
end
def self.bootstrap
puts "MyApp #{VERSION} 启动"
end
end
# 用 :: 访问模块内的常量与类
u = MyApp::User.new("Alice")
puts u.name # Alice
MyApp.bootstrap # MyApp 1.0.0 启动
puts MyApp::VERSION # 1.0.0
# 好处:避免和别人的 User、VERSION 撞名
# Gem 命名约定:模块名 = gem 名(大驼峰),如 ActiveSupport、Net::HTTPRuby 的标准库几乎都是命名空间形式:Net::HTTP、File::Utils、Time。Gems 也遵循同样约定——ActiveRecord::Base、Sidekiq::Worker。
2. 模块作为 mixin
这是模块最有用的能力。把通用方法塞进模块,任何类都能 include 进去复用:
# 模块作为 mixin:把方法"塞"进类
module Greetable
def greet
"Hi, I'm #{name}"
end
end
class User
include Greetable # 把 Greetable 的方法变成 User 的实例方法
attr_reader :name
def initialize(name); @name = name; end
end
User.new("Alice").greet # Hi, I'm Alice
# extend:把方法变成类方法
module Loggable
def log(msg); puts msg; end
end
class Service
extend Loggable # Service.log "..." (类方法)
end
Service.log("started") # startedinclude 把方法变成实例方法,extend 把方法变成类方法。这两条用得最多。
3. include / extend / prepend 三兄弟
三种混入方式在方法查找链(ancestors)里的位置不同:
# include / extend / prepend 的查找顺序差异
# include:插入到"类之上"
# 子类 super → 父类 → ... → 模块
module M1; def m; "M1"; end; end
class A; include M1; def m; "A"; end; end
A.new.m # "A"(类自己的优先)
A.ancestors # [A, M1, Object, ...]
# prepend:插入到"类之前"(可以 wrap 类的方法)
module M2
def m
puts "before"
super # 调用被包裹的原始方法
puts "after"
end
end
class B; prepend M2; def m; puts "B"; end; end
B.new.m
# before
# B
# after
B.ancestors # [M2, B, Object, ...](M2 在 B 之前!)
# extend:把模块方法加成类的"类方法"(不影响实例方法)关键:prepend 把模块放在类之前,所以模块的同名方法能"包裹"类的方法(用 super 调原方法)。这是写日志、缓存、性能监控等"切面"代码的核心技巧,Rails 5+ 大量用它。
4. Comparable:实现一个方法,获得一堆
Ruby 最优雅的设计之一——只要你的类实现 <=>(spaceship 运算符)并 include Comparable,就免费获得 < > == between? clamp 等十几个方法:
# Comparable:只要实现 <=> (spaceship),就能用 < > == between? 等
class Length
include Comparable
attr_reader :meters
def initialize(meters); @meters = meters; end
# Comparable 要求你实现这个方法
def <=>(other)
meters <=> other.meters
end
end
a = Length.new(5)
b = Length.new(10)
puts a < b # true(Comparable 自动提供!)
puts a == b # false
puts a.between?(1, 100) # true
puts [b, a].sort # [5m, 10m](自动用 <=> 排序)
puts a.clamp(Length.new(7), Length.new(20)) # 7(夹紧到范围)5. Enumerable:集合的瑞士军刀
实现 each 并 include Enumerable,你的类就拥有 map select reduce min max sort group_by partition 等几十个方法。Array、Hash 都靠它:
# Enumerable:实现 each,就能用 map/select/reduce/min/max 等
class Team
include Enumerable
def initialize(members); @members = members; end
# Enumerable 要求你实现 each
def each(&block)
@members.each(&block)
end
end
team = Team.new(["Alice", "Bob", "Cathy"])
# 现在 team 自动拥有了几十个方法!
team.map(&:upcase) # ["ALICE", "BOB", "CATHY"]
team.select { |n| n.length > 4 } # ["Alice", "Cathy"]
team.reduce(:+) # "AliceBobCathy"
team.min_by(&:length) # "Bob"
team.sort # ["Alice", "Bob", "Cathy"]
team.group_by(&:length) # {5=>["Alice", "Cathy"], 3=>["Bob"]}
team.tally # {"Alice"=>1, "Bob"=>1, "Cathy"=>1}(计数)Enumerable 还提供 lazy(惰性求值)、flat_map、chunk_while、tally(计数,2.7+)等高级方法。它是 Ruby 函数式编程的主力。
6. module_function:既是模块方法也是实例方法
# module_function:让模块方法既能当模块方法,又能当实例方法
module MathUtils
def square(x); x * x; end
module_function :square # 现在 MathUtils.square(5) 也行
end
MathUtils.square(5) # 5
class Calc
include MathUtils # include 后也能当实例方法用
end
Calc.new.square(6) # 36
# 典型例子:Math 模块
Math.sqrt(16) # 2.0
Math::PI # 3.14159...这是 C 风格"工具函数"的写法——既可以直接 Module.func 调用,也可以 include 后当实例方法用。标准库 Math 就是这么实现的。
小结
模块是 Ruby 组织代码与复用方法的核心工具。命名空间防冲突,mixin 实现多继承,include/extend/prepend 控制查找位置,Comparable/Enumerable 让你"实现一个方法,获得几十个"。下一篇看 Ruby 的包管理——Gems 与 Bundler。
← 上一篇 Ruby 类与面向对象
下一篇 Ruby Gems 与 Bundler →