强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 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

特性LambdaProc
参数检查严格(数量必须匹配)宽松(自动补 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 动手练习

  1. 实现 retry 方法
def with_retry(max_attempts: 3, &block)
  # 你的代码:实现重试逻辑
end
  1. 实现 tap 方法
def my_tap(obj)
  # 你的代码:实现类似 Object#tap 的功能
end
  1. 函数组合
# 实现 pipe 方法,将多个函数组合成管道
def pipe(*functions)
  # 你的代码...
end

6.11 本章小结

要点说明
方法定义def...end,支持默认参数、可变参数、关键字参数
返回值隐式返回最后一个表达式,return 提前返回
可见性publicprotectedprivate
单例方法为特定对象定义的方法
Proc块的对象化,参数宽松
Lambda特殊的 Proc,参数严格,return 行为不同
方法链返回 self 支持链式调用

📖 扩展阅读


上一章← 第 05 章:控制流程 下一章第 07 章:数组与哈希 →