Skip to content

Ruby 方法

方法是组织代码的基本单元,用于封装可重用的代码逻辑。Ruby中的方法功能强大且灵活,支持多种参数形式和高级特性。本章将详细介绍Ruby中方法的定义、调用和高级用法。

🎯 方法基础

方法定义

ruby
# 基本方法定义
def greet
  puts "Hello, World!"
end

# 带参数的方法
def greet_person(name)
  puts "Hello, #{name}!"
end

# 带默认参数的方法
def greet_with_title(name, title = "先生/女士")
  puts "Hello, #{title} #{name}!"
end

# 调用方法
greet                    # Hello, World!
greet_person("张三")      # Hello, 张三!
greet_with_title("李四")  # Hello, 先生/女士 李四!
greet_with_title("王五", "经理")  # Hello, 经理 王五!

方法返回值

ruby
# 方法自动返回最后一个表达式的值
def add(a, b)
  a + b  # 自动返回
end

# 显式返回
def multiply(a, b)
  return a * b
end

# 多值返回
def get_name_and_age
  ["张三", 25]
end

# 使用并行赋值接收多值返回
name, age = get_name_and_age
puts "姓名: #{name}, 年龄: #{age}"  # 姓名: 张三, 年龄: 25

# 条件返回
def divide(a, b)
  return nil if b == 0
  a / b
end

puts divide(10, 2)  # 5
puts divide(10, 0)  # nil

📦 参数类型

位置参数

ruby
# 必需的位置参数
def create_user(name, age, email)
  {name: name, age: age, email: email}
end

user = create_user("张三", 25, "zhangsan@example.com")

# 可选的位置参数(默认值)
def greet_person(name, greeting = "Hello", punctuation = "!")
  "#{greeting}, #{name}#{punctuation}"
end

puts greet_person("张三")                    # Hello, 张三!
puts greet_person("李四", "你好")             # 你好, 李四!
puts greet_person("王五", "Bonjour", ".")    # Bonjour, 王五.

关键字参数

ruby
# 关键字参数(Ruby 2.0+)
def create_user(name:, age:, email: nil)
  {name: name, age: age, email: email}
end

# 调用时必须使用关键字
user = create_user(name: "张三", age: 25)
user = create_user(age: 30, name: "李四", email: "lisi@example.com")

# 带默认值的关键字参数
def greet_person(name:, greeting: "Hello", punctuation: "!")
  "#{greeting}, #{name}#{punctuation}"
end

puts greet_person(name: "张三")  # Hello, 张三!

可变参数

ruby
# 可变参数(splat operator)
def sum(*numbers)
  numbers.reduce(0) { |total, n| total + n }
end

puts sum(1, 2, 3)        # 6
puts sum(1, 2, 3, 4, 5)  # 15
puts sum()               # 0

# 混合使用位置参数和可变参数
def greet_and_list(name, *items)
  puts "Hello, #{name}!"
  puts "你的物品:"
  items.each { |item| puts "- #{item}" }
end

greet_and_list("张三", "苹果", "香蕉", "橙子")
# Hello, 张三!
# 你的物品:
# - 苹果
# - 香蕉
# - 橙子

关键字可变参数

ruby
# 关键字可变参数(double splat operator)
def configure(**options)
  options.each { |key, value| puts "#{key}: #{value}" }
end

configure(host: "localhost", port: 3000, debug: true)
# host: localhost
# port: 3000
# debug: true

# 混合使用多种参数类型
def complex_method(required, optional = "default", *args, **kwargs)
  puts "必需参数: #{required}"
  puts "可选参数: #{optional}"
  puts "可变参数: #{args}"
  puts "关键字参数: #{kwargs}"
end

complex_method("必需", "可选", "额外1", "额外2", key1: "值1", key2: "值2")
# 必需参数: 必需
# 可选参数: 可选
# 可变参数: ["额外1", "额外2"]
# 关键字参数: {:key1=>"值1", :key2=>"值2"}

参数解构

ruby
# 数组参数解构
def process_coordinates((x, y))
  puts "X坐标: #{x}, Y坐标: #{y}"
end

point = [10, 20]
process_coordinates(point)  # X坐标: 10, Y坐标: 20

# 哈希参数解构
def process_person(name:, age:)
  puts "姓名: #{name}, 年龄: #{age}"
end

person = {name: "张三", age: 25}
process_person(**person)  # 姓名: 张三, 年龄: 25

🔧 方法高级特性

方法别名

ruby
# 为方法创建别名
def original_method
  "原始方法"
end

alias_method :aliased_method, :original_method

puts original_method  # 原始方法
puts aliased_method   # 原始方法

# 在类中使用别名
class Calculator
  def add(a, b)
    a + b
  end
  
  alias_method :plus, :add
end

calc = Calculator.new
puts calc.add(2, 3)  # 5
puts calc.plus(2, 3) # 5

方法可见性

ruby
class MyClass
  def public_method
    "公共方法"
  end
  
  private
  
  def private_method
    "私有方法"
  end
  
  protected
  
  def protected_method
    "受保护方法"
  end
  
  public
  
  def call_private
    private_method  # 可以在类内部调用私有方法
  end
end

obj = MyClass.new
puts obj.public_method     # 公共方法
puts obj.call_private      # 私有方法
# obj.private_method       # 错误: private method
# obj.protected_method     # 错误: protected method

方法缺失处理

ruby
class FlexibleObject
  def initialize
    @data = {}
  end
  
  # 处理未定义的方法调用
  def method_missing(method_name, *args, &block)
    if method_name.to_s.end_with?('=')
      # setter方法
      key = method_name.to_s.chomp('=').to_sym
      @data[key] = args.first
    else
      # getter方法
      @data[method_name]
    end
  end
  
  # 告诉Ruby哪些方法是可响应的
  def respond_to_missing?(method_name, include_private = false)
    true
  end
end

obj = FlexibleObject.new
obj.name = "张三"
obj.age = 25
puts obj.name  # 张三
puts obj.age   # 25

🔄 代码块和Proc

方法接受代码块

ruby
# 方法接受代码块参数
def with_logging
  puts "开始执行"
  yield if block_given?
  puts "执行完成"
end

with_logging { puts "执行具体操作" }
# 开始执行
# 执行具体操作
# 执行完成

# 显式接受块参数
def with_timer(&block)
  start_time = Time.now
  result = block.call
  end_time = Time.now
  puts "执行时间: #{end_time - start_time}秒"
  result
end

result = with_timer { sleep(1); "完成" }
puts result

Proc和Lambda

ruby
# 创建Proc
my_proc = Proc.new { |x| x * 2 }
puts my_proc.call(5)  # 10

# 创建Lambda
my_lambda = lambda { |x| x * 2 }
puts my_lambda.call(5)  # 10

# 简写Lambda语法
my_lambda2 = ->(x) { x * 2 }
puts my_lambda2.call(5)  # 10

# Proc和Lambda的区别
def test_return
  proc = Proc.new { return "Proc返回" }
  lambda = -> { return "Lambda返回" }
  
  proc.call  # 会从test_return方法返回
  lambda.call  # 只从lambda内部返回
  "方法结束"
end

# puts test_return  # Proc返回

方法传递代码块

ruby
# 将块传递给其他方法
def process_array(array, &block)
  array.map(&block)
end

numbers = [1, 2, 3, 4, 5]
squared = process_array(numbers) { |n| n * n }
puts squared.inspect  # [1, 4, 9, 16, 25]

# 使用符号快捷方式
words = ["hello", "world", "ruby"]
upcased = words.map(&:upcase)
puts upcased.inspect  # ["HELLO", "WORLD", "RUBY"]

🎯 方法实践示例

配置构建器

ruby
class ConfigBuilder
  def initialize
    @config = {}
  end
  
  def method_missing(method_name, *args, &block)
    if method_name.to_s.end_with?('=')
      key = method_name.to_s.chomp('=').to_sym
      @config[key] = args.first
    elsif block_given?
      @config[method_name] = block
    elsif args.length == 1
      @config[method_name] = args.first
    elsif args.length > 1
      @config[method_name] = args
    else
      @config[method_name]
    end
  end
  
  def respond_to_missing?(method_name, include_private = false)
    true
  end
  
  def build
    @config.dup
  end
  
  def configure(&block)
    instance_eval(&block) if block_given?
    self
  end
end

# 使用配置构建器
config = ConfigBuilder.new.configure do
  database_url "postgresql://localhost/myapp"
  port 3000
  debug true
  after_initialize { puts "初始化完成" }
end.build

puts config
# {:database_url=>"postgresql://localhost/myapp", :port=>3000, :debug=>true, :after_initialize=>#<Proc:0x000000010d0a8f80>}

链式调用方法

ruby
class QueryBuilder
  def initialize
    @query = {}
  end
  
  def select(*fields)
    @query[:select] = fields
    self  # 返回self支持链式调用
  end
  
  def from(table)
    @query[:from] = table
    self
  end
  
  def where(condition)
    @query[:where] = condition
    self
  end
  
  def limit(count)
    @query[:limit] = count
    self
  end
  
  def order_by(field, direction = :asc)
    @query[:order] = {field => direction}
    self
  end
  
  def build
    query_string = "SELECT "
    query_string += @query[:select] ? @query[:select].join(", ") : "*"
    query_string += " FROM #{@query[:from]}" if @query[:from]
    query_string += " WHERE #{@query[:where]}" if @query[:where]
    query_string += " ORDER BY #{@query[:order].keys.first} #{@query[:order].values.first}" if @query[:order]
    query_string += " LIMIT #{@query[:limit]}" if @query[:limit]
    query_string
  end
end

# 使用链式调用
query = QueryBuilder.new
  .select(:name, :age)
  .from(:users)
  .where("age > 18")
  .order_by(:name, :desc)
  .limit(10)
  .build

puts query
# SELECT name, age FROM users WHERE age > 18 ORDER BY name desc LIMIT 10

装饰器模式方法

ruby
class TextProcessor
  def initialize(text)
    @text = text
  end
  
  def process(&block)
    if block_given?
      @text = block.call(@text)
    end
    self
  end
  
  def upcase
    process { |text| text.upcase }
  end
  
  def reverse
    process { |text| text.reverse }
  end
  
  def strip
    process { |text| text.strip }
  end
  
  def replace(pattern, replacement)
    process { |text| text.gsub(pattern, replacement) }
  end
  
  def result
    @text
  end
end

# 使用文本处理器
result = TextProcessor.new("  hello world  ")
  .strip
  .upcase
  .replace(/O/, "0")
  .reverse
  .result

puts result  # DL0W 0LLEH

🛡️ 方法安全和验证

参数验证

ruby
class SafeMethods
  def self.divide(dividend, divisor)
    # 参数类型验证
    raise ArgumentError, "被除数必须是数字" unless dividend.is_a?(Numeric)
    raise ArgumentError, "除数必须是数字" unless divisor.is_a?(Numeric)
    
    # 业务逻辑验证
    raise ArgumentError, "除数不能为零" if divisor == 0
    
    dividend / divisor
  end
  
  def self.create_user(name, age, email = nil)
    # 验证必需参数
    raise ArgumentError, "姓名不能为空" if name.nil? || name.empty?
    raise ArgumentError, "年龄必须是正数" unless age.is_a?(Integer) && age > 0
    
    # 验证可选参数
    if email && !email.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
      raise ArgumentError, "邮箱格式不正确"
    end
    
    {name: name, age: age, email: email}
  end
end

# 使用安全方法
begin
  result = SafeMethods.divide(10, 2)
  puts result  # 5
  
  user = SafeMethods.create_user("张三", 25, "zhangsan@example.com")
  puts user
rescue ArgumentError => e
  puts "错误: #{e.message}"
end

方法缓存

ruby
class CachedMethods
  def initialize
    @cache = {}
  end
  
  def expensive_calculation(n)
    # 模拟昂贵的计算
    cache_key = "expensive_#{n}"
    
    if @cache.key?(cache_key)
      puts "从缓存获取结果"
      return @cache[cache_key]
    end
    
    puts "执行计算"
    result = (1..n).reduce(1) { |acc, i| acc * i }  # 计算阶乘
    @cache[cache_key] = result
    result
  end
  
  def clear_cache
    @cache.clear
  end
end

# 使用缓存方法
calculator = CachedMethods.new
puts calculator.expensive_calculation(5)  # 执行计算, 120
puts calculator.expensive_calculation(5)  # 从缓存获取结果, 120

🎯 方法最佳实践

1. 方法设计原则

ruby
# 好的做法:单一职责原则
class UserManager
  def create_user(name, email)
    validate_user_data(name, email)
    user = build_user(name, email)
    save_user(user)
  end
  
  private
  
  def validate_user_data(name, email)
    raise ArgumentError, "姓名不能为空" if name.nil? || name.empty?
    raise ArgumentError, "邮箱格式不正确" unless valid_email?(email)
  end
  
  def build_user(name, email)
    {name: name, email: email, created_at: Time.now}
  end
  
  def save_user(user)
    # 保存用户逻辑
    puts "用户已保存: #{user[:name]}"
  end
  
  def valid_email?(email)
    email.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
  end
end

2. 参数处理最佳实践

ruby
# 使用关键字参数提高可读性
def create_http_request(
  method:,
  url:,
  headers: {},
  body: nil,
  timeout: 30,
  retries: 3
)
  {
    method: method.upcase,
    url: url,
    headers: headers,
    body: body,
    timeout: timeout,
    retries: retries
  }
end

# 调用时清晰明了
request = create_http_request(
  method: "post",
  url: "https://api.example.com/users",
  headers: {"Content-Type" => "application/json"},
  body: '{"name": "张三"}',
  timeout: 60
)

# 使用选项哈希模式
def configure_database(options = {})
  config = {
    host: options[:host] || "localhost",
    port: options[:port] || 5432,
    database: options[:database] || "myapp",
    username: options[:username] || "user",
    password: options[:password] || nil
  }
  
  # 配置逻辑
  config
end

3. 错误处理

ruby
class ApiClient
  def get_user(user_id)
    # 返回结果或错误
    response = make_request("/users/#{user_id}")
    
    case response.status
    when 200
      parse_user_data(response.body)
    when 404
      raise UserNotFoundError, "用户不存在: #{user_id}"
    when 403
      raise PermissionError, "权限不足"
    when 500
      raise ServerError, "服务器错误"
    else
      raise ApiError, "未知错误: #{response.status}"
    end
  end
  
  private
  
  def make_request(endpoint)
    # 模拟HTTP请求
    OpenStruct.new(status: 200, body: '{"id": 1, "name": "张三"}')
  end
  
  def parse_user_data(json_data)
    JSON.parse(json_data)
  rescue JSON::ParserError => e
    raise DataParsingError, "数据解析失败: #{e.message}"
  end
end

# 自定义异常类
class ApiError < StandardError; end
class UserNotFoundError < ApiError; end
class PermissionError < ApiError; end
class ServerError < ApiError; end
class DataParsingError < ApiError; end

4. 文档化方法

ruby
class MathUtils
  # 计算两个数的最大公约数
  # 
  # 使用欧几里得算法计算最大公约数
  # 
  # @param a [Integer] 第一个数
  # @param b [Integer] 第二个数
  # 
  # @return [Integer] 最大公约数
  # 
  # @example
  #   MathUtils.gcd(48, 18)  # => 6
  #   MathUtils.gcd(17, 13)  # => 1
  # 
  # @raise [ArgumentError] 当参数不是正整数时抛出
  def self.gcd(a, b)
    # 参数验证
    raise ArgumentError, "参数必须是正整数" unless a.is_a?(Integer) && b.is_a?(Integer)
    raise ArgumentError, "参数必须大于0" unless a > 0 && b > 0
    
    # 欧几里得算法
    while b != 0
      a, b = b, a % b
    end
    a
  end
  
  # 计算数组的平均值
  # 
  # @param numbers [Array<Numeric>] 数字数组
  # 
  # @return [Float] 平均值
  # 
  # @example
  #   MathUtils.average([1, 2, 3, 4, 5])  # => 3.0
  #   MathUtils.average([10, 20, 30])     # => 20.0
  # 
  # @raise [ArgumentError] 当数组为空或包含非数字时抛出
  def self.average(numbers)
    # 参数验证
    raise ArgumentError, "参数必须是数组" unless numbers.is_a?(Array)
    raise ArgumentError, "数组不能为空" if numbers.empty?
    raise ArgumentError, "数组必须包含数字" unless numbers.all? { |n| n.is_a?(Numeric) }
    
    numbers.sum.to_f / numbers.length
  end
end

📚 下一步学习

掌握了Ruby方法后,建议继续学习:

继续您的Ruby学习之旅吧!

本站内容仅供学习和研究使用。