Ruby 入门指南 / 第 06 章:方法
第 06 章:方法
“方法是行为的封装,是对象之间的通信契约。”
6.1 方法定义
6.1.1 基本语法
# 方法定义
def greet(name)
"Hello, #{name}!"
end
puts greet("Ruby") # => "Hello, Ruby!"
# 方法命名约定
# - 小写字母开头,单词之间用下划线连接(snake_case)
# - 返回布尔值的方法以 ? 结尾:empty?, nil?, even?
# - 修改自身的方法以 ! 结尾:map!, gsub!, reverse!
# - 设置方法以 = 结尾:name=
def valid? # 布尔方法
true
end
def capitalize! # 危险方法(原地修改)
# ...
end
6.1.2 方法参数
# 必需参数
def add(a, b)
a + b
end
# 默认参数
def greet(name = "World")
"Hello, #{name}!"
end
greet # => "Hello, World!"
greet("Matz") # => "Hello, Matz!"
# 多个默认参数
def log(message, level = :info, timestamp = Time.now)
"[#{timestamp}] [#{level.upcase}] #{message}"
end
# 可变参数(splat)
def sum(*numbers)
numbers.reduce(0, :+)
end
sum(1, 2, 3) # => 6
sum(1, 2, 3, 4, 5) # => 15
# 混合参数
def process(required, *optional, keyword:)
puts "required: #{required}"
puts "optional: #{optional}"
puts "keyword: #{keyword}"
end
process("a", "b", "c", keyword: "value")
# required: a
# optional: ["b", "c"]
# keyword: value
# 双 splat(关键字参数收集)
def configure(**options)
options.each { |k, v| puts "#{k}: #{v}" }
end
configure(host: "localhost", port: 3000, debug: true)
6.1.3 关键字参数(Ruby 2.0+)
# 关键字参数
def connect(host:, port: 80, ssl: false)
puts "Connecting to #{host}:#{port} (SSL: #{ssl})"
end
connect(host: "example.com")
# Connecting to example.com:80 (SSL: false)
connect(host: "example.com", port: 443, ssl: true)
# Connecting to example.com:443 (SSL: true)
# 必需关键字参数
def create_user(name:, age:, email:)
# 必须提供这三个参数
end
# **rest 收集所有关键字参数
def method_with_options(required, **opts)
puts "Required: #{required}"
opts.each { |k, v| puts "#{k}: #{v}" }
end
method_with_options("test", color: "red", size: "large")
# Ruby 3.0+ 关键字参数分离
# 之前:Hash 自动转换为关键字参数(已废弃)
# 现在:必须显式使用 ** 或关键字参数
6.1.4 参数解构
# 数组解构
def destruct_array((first, second))
"First: #{first}, Second: #{second}"
end
destruct_array([1, 2]) # => "First: 1, Second: 2"
destruct_array([1, 2, 3, 4]) # => "First: 1, Second: 2"
# 嵌套解构
def destruct_nested((a, (b, c)))
"#{a}-#{b}-#{c}"
end
destruct_nested([1, [2, 3]]) # => "1-2-3"
6.2 返回值
6.2.1 隐式返回
# Ruby 方法返回最后一个表达式的值
def add(a, b)
a + b # 隐式返回
end
# 条件分支的隐式返回
def classify(number)
if number > 0
"正数"
elsif number < 0
"负数"
else
"零"
end
end
6.2.2 显式返回
# return 提前返回
def find_user(id)
return nil if id.nil?
return nil if id <= 0
# 查找用户...
user = database.find(id)
return nil unless user
user
end
# 多个 return(用于守卫条件)
def process(data)
return "数据为空" if data.nil? || data.empty?
return "格式错误" unless data.is_a?(Array)
data.map(&:to_s)
end
# return 返回多个值(实际返回数组)
def divmod(a, b)
return a / b, a % b
end
quotient, remainder = divmod(10, 3)
puts "商: #{quotient}, 余: #{remainder}"
# => 商: 3, 余: 1
6.2.3 返回值最佳实践
# ✅ 好的做法:清晰的返回类型
def active_users
User.where(active: true)
end
# ✅ 守卫条件提前返回
def process_order(order)
return if order.nil?
return unless order.valid?
return if order.processed?
# 处理逻辑...
end
# ❌ 避免:不必要的 return
def calculate(x)
return x * 2 # 不需要 return
end
# ✅ 更 Ruby 的方式
def calculate(x)
x * 2
end
6.3 方法可见性
6.3.1 public / protected / private
class User
def initialize(name, password)
@name = name
@password = encrypt(password)
end
# public 方法:外部可调用
def name
@name
end
def authenticate(password)
@password == encrypt(password)
end
protected
# protected 方法:只能被同类或子类的对象调用
def age
@age
end
private
# private 方法:只能在对象内部调用
def encrypt(password)
Digest::SHA256.hexdigest(password)
end
def validate
# ...
end
end
user = User.new("Alice", "secret")
user.name # => "Alice"
user.authenticate("secret") # => true
# user.encrypt("x") # => NoMethodError (private method)
6.3.2 private 的特殊性
class Demo
private
def secret
"secret"
end
def test
secret # ✅ 可以调用
self.secret # ❌ Ruby 2.7 之前报错,2.7+ 允许
end
end
# Ruby 2.7+ 的变化
class Demo2
def test
self.secret # ✅ Ruby 2.7+ 允许
end
private
def secret
"secret"
end
end
6.4 单例方法(Singleton Methods)
6.4.1 为单个对象定义方法
# 单例方法只属于特定对象
str = "hello"
def str.shout
upcase + "!!!"
end
puts str.shout # => "HELLO!!!"
# 其他字符串没有这个方法
other = "world"
# other.shout # => NoMethodError
# 为类的实例定义单例方法
class Dog
def initialize(name)
@name = name
end
end
buddy = Dog.new("Buddy")
rex = Dog.new("Rex")
def buddy.speak
"Woof! I'm #{@name}"
end
puts buddy.speak # => "Woof! I'm Buddy"
# rex.speak # => NoMethodError
6.4.2 类方法的本质
# 类方法就是类对象的单例方法
class MyClass
def self.my_class_method
"I'm a class method"
end
end
# 等价于
class MyClass
end
def MyClass.my_class_method
"I'm a class method"
end
# 另一种写法
class MyClass
class << self
def my_class_method
"I'm a class method"
end
end
end
6.5 Proc 对象
6.5.1 创建 Proc
# Proc 是块的对象化
# 方式 1:Proc.new
square = Proc.new { |n| n ** 2 }
square.call(5) # => 25
# 方式 2:proc 方法
cube = proc { |n| n ** 3 }
cube.call(5) # => 125
# 方式 3:lambda 表达式(Lambda)
double = -> (n) { n * 2 }
double.call(5) # => 10
# 方式 4:使用 & 将块转为 Proc
def make_proc(&block)
block
end
my_proc = make_proc { |n| n * 3 }
my_proc.call(5) # => 15
6.5.2 调用 Proc
my_proc = proc { |n| n * 2 }
# 多种调用方式
my_proc.call(5) # => 10
my_proc.(5) # => 10(语法糖)
my_proc[5] # => 10
my_proc === 5 # => 10(用于 case 语句)
my_proc.yield(5) # => 10
# 参数灵活性(Proc 不检查参数数量)
flexible = proc { |a, b, c| [a, b, c] }
flexible.call(1) # => [1, nil, nil]
flexible.call(1, 2) # => [1, 2, nil]
flexible.call(1, 2, 3) # => [1, 2, 3]
flexible.call(1, 2, 3, 4) # => [1, 2, 3](忽略多余参数)
6.5.3 Proc 作为闭包
# Proc 捕获创建时的作用域
def make_counter
count = 0
{
increment: -> { count += 1 },
decrement: -> { count -= 1 },
current: -> { count }
}
end
counter = make_counter
counter[:increment].call
counter[:increment].call
counter[:increment].call
counter[:current].call # => 3
counter[:decrement].call
counter[:current].call # => 2
6.6 Lambda
6.6.1 Lambda 语法
# Lambda 语法
square = lambda { |n| n ** 2 }
square = -> (n) { n ** 2 } # 推荐写法
square = -> n { n ** 2 } # 单参数可以省略括号
# 多参数
add = -> (a, b) { a + b }
add.call(3, 5) # => 8
# 无参数
greet = -> { "Hello!" }
greet.call # => "Hello!"
# 多行 Lambda
process = -> (data) {
result = data.map(&:upcase)
result.join(", ")
}
6.6.2 Lambda vs Proc
| 特性 | Lambda | Proc |
|---|---|---|
| 参数检查 | 严格(数量必须匹配) | 宽松(自动补 nil) |
| return 行为 | 从 Lambda 返回 | 从定义 Proc 的方法返回 |
| 创建语法 | -> { } 或 lambda { } | Proc.new { } 或 proc { } |
# 1. 参数检查差异
lam = -> (a, b) { [a, b] }
prc = proc { |a, b| [a, b] }
lam.call(1, 2) # => [1, 2]
# lam.call(1) # => ArgumentError(Lambda 严格检查)
prc.call(1) # => [1, nil](Proc 宽松处理)
# 2. return 行为差异
def test_proc
p = proc { return "from proc" }
p.call
"after proc" # 不会执行!
end
def test_lambda
l = -> { return "from lambda" }
l.call
"after lambda" # 会执行!
end
test_proc # => "from proc"(return 退出了整个方法)
test_lambda # => "after lambda"(return 只退出 Lambda)
6.7 方法作为对象
6.7.1 方法对象
class Calculator
def add(a, b)
a + b
end
def multiply(a, b)
a * b
end
end
calc = Calculator.new
# 获取方法对象
add_method = method(:add)
add_method.class # => Method
add_method.call(3, 5) # => 8
# UnboundMethod
unbound = Calculator.instance_method(:multiply)
unbound.class # => UnboundMethod
bound = unbound.bind(calc)
bound.call(3, 5) # => 15
# 方法转 Proc(使用 &)
[1, 2, 3].map(&method(:puts))
6.7.2 Symbol#to_proc
# &:method_name 是 Symbol#to_proc 的语法糖
# 以下两种写法等价
[1, 2, 3].map { |n| n.to_s }
[1, 2, 3].map(&:to_s)
# 适用场景
["hello", "world"].map(&:upcase) # => ["HELLO", "WORLD"]
[1, 2, 3, 4, 5].select(&:even?) # => [2, 4]
["a", "bb", "ccc"].sort_by(&:length) # => ["a", "bb", "ccc"]
[1, nil, 2, nil, 3].compact # => [1, 2, 3]
6.8 方法组合
6.8.1 方法链
# 方法链让代码更流畅
class QueryBuilder
def initialize
@conditions = []
@order = nil
@limit = nil
end
def where(condition)
@conditions << condition
self # 返回 self 以支持链式调用
end
def order(field)
@order = field
self
end
def limit(n)
@limit = n
self
end
def build
sql = "SELECT * FROM records"
sql += " WHERE #{@conditions.join(' AND ')}" unless @conditions.empty?
sql += " ORDER BY #{@order}" if @order
sql += " LIMIT #{@limit}" if @limit
sql
end
end
query = QueryBuilder.new
.where("age > 18")
.where("active = true")
.order("created_at DESC")
.limit(10)
.build
puts query
# SELECT * FROM records WHERE age > 18 AND active = true ORDER BY created_at DESC LIMIT 10
6.8.2 方法引用和组合
# Ruby 2.6+ 方法引用运算符
# 方法组合(使用 >> 或 <<)
upcase = -> (s) { s.upcase }
exclaim = -> (s) { "#{s}!" }
# 组合函数
shout = upcase >> exclaim # 先 upcase 再 exclaim
shout.call("hello") # => "HELLO!"
whisper = exclaim << upcase # 等价(顺序相反)
whisper.call("hello") # => "HELLO!"
6.9 实际业务场景
6.9.1 配置 DSL
class AppConfig
attr_reader :settings
def initialize
@settings = {}
end
def method_missing(name, *args)
if name.to_s.end_with?("=")
key = name.to_s.chomp("=").to_sym
@settings[key] = args.first
elsif args.empty?
@settings[name]
else
super
end
end
def respond_to_missing?(name, include_private = false)
true
end
end
# 使用 DSL 配置
config = AppConfig.new
config.app_name = "MyApp"
config.version = "1.0.0"
config.debug = true
puts config.app_name # => "MyApp"
puts config.version # # => "1.0.0"
6.9.2 回调和钩子
class EventEmitter
def initialize
@listeners = Hash.new { |h, k| h[k] = [] }
end
def on(event, &handler)
@listeners[event] << handler
end
def emit(event, *args)
@listeners[event].each { |handler| handler.call(*args) }
end
end
# 使用
emitter = EventEmitter.new
emitter.on(:user_created) { |user| puts "Welcome, #{user}!" }
emitter.on(:user_created) { |user| puts "Sending email to #{user}" }
emitter.emit(:user_created, "Alice")
6.10 动手练习
- 实现 retry 方法
def with_retry(max_attempts: 3, &block)
# 你的代码:实现重试逻辑
end
- 实现 tap 方法
def my_tap(obj)
# 你的代码:实现类似 Object#tap 的功能
end
- 函数组合
# 实现 pipe 方法,将多个函数组合成管道
def pipe(*functions)
# 你的代码...
end
6.11 本章小结
| 要点 | 说明 |
|---|---|
| 方法定义 | def...end,支持默认参数、可变参数、关键字参数 |
| 返回值 | 隐式返回最后一个表达式,return 提前返回 |
| 可见性 | public、protected、private |
| 单例方法 | 为特定对象定义的方法 |
| Proc | 块的对象化,参数宽松 |
| Lambda | 特殊的 Proc,参数严格,return 行为不同 |
| 方法链 | 返回 self 支持链式调用 |
📖 扩展阅读
上一章:← 第 05 章:控制流程 下一章:第 07 章:数组与哈希 →