Skip to content

Ruby 块及模块(Module)

块(Block)和模块(Module)是Ruby中两个重要的语言特性。块提供了代码复用和回调机制,而模块则实现了代码共享和命名空间管理。本章将详细介绍Ruby中块和模块的概念、使用方法以及最佳实践。

📦 块(Block)

什么是块?

块是Ruby中的一种特殊语法结构,它是一段可以传递给方法的代码。块不是对象,但可以被方法调用和执行。

ruby
# 使用花括号定义单行块
[1, 2, 3].each { |n| puts n }

# 使用do...end定义多行块
[1, 2, 3].each do |n|
  puts "数字: #{n}"
end

块参数

ruby
# 无参数块
3.times { puts "Hello" }

# 单参数块
[1, 2, 3].each { |n| puts n }

# 多参数块
{a: 1, b: 2}.each { |key, value| puts "#{key}: #{value}" }

# 参数解构
coordinates = [[1, 2], [3, 4], [5, 6]]
coordinates.each { |(x, y)| puts "坐标: (#{x}, #{y})" }

yield语句

ruby
# 方法中使用yield调用块
def my_method
  puts "方法开始"
  yield if block_given?
  puts "方法结束"
end

my_method { puts "块中的代码" }
# 输出:
# 方法开始
# 块中的代码
# 方法结束

# 带参数的yield
def calculate(a, b)
  result = yield(a, b) if block_given?
  puts "计算结果: #{result}"
end

calculate(10, 5) { |x, y| x + y }  # 计算结果: 15
calculate(10, 5) { |x, y| x * y }  # 计算结果: 50

块的返回值

ruby
# 块有返回值
def with_calculation
  result = yield if block_given?
  puts "块返回: #{result}"
end

with_calculation { 2 + 3 }  # 块返回: 5

# 多次调用块
def repeat(n)
  results = []
  n.times do |i|
    results << yield(i) if block_given?
  end
  results
end

results = repeat(3) { |i| i * i }
puts results.inspect  # [0, 1, 4]

块局部变量

ruby
# 块局部变量
total = 0
[1, 2, 3].each do |n; block_local|
  block_local = n * 2
  total += block_local
  puts "局部变量: #{block_local}, 总计: #{total}"
end

# 块参数与外部变量同名
x = 10
[1, 2, 3].each do |x|
  puts "块参数x: #{x}"  # 1, 2, 3
end
puts "外部变量x: #{x}"  # 10

🔧 块作为参数

&block参数

ruby
# 显式接受块参数
def explicit_block(&block)
  if block_given?
    puts "块存在"
    block.call  # 调用块
  else
    puts "没有块"
  end
end

explicit_block { puts "Hello from block" }
explicit_block

# 将块传递给其他方法
def pass_block_to_other_method(&block)
  [1, 2, 3].map(&block) if block
end

result = pass_block_to_other_method { |n| n * 2 }
puts result.inspect  # [2, 4, 6]

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_obj = lambda { return "Lambda返回" }
  
  proc_result = proc.call
  lambda_result = lambda_obj.call
  
  puts "Proc结果: #{proc_result}"
  puts "Lambda结果: #{lambda_result}"
  "方法结束"
end

# test_return会返回"Proc返回"而不是"方法结束"

块转换

ruby
# 将符号转换为块
numbers = [1, 2, 3, 4, 5]
squared = numbers.map(&:square)  # 等同于 numbers.map { |n| n.square }

# 自定义方法支持符号到块的转换
class Integer
  def square
    self * self
  end
end

numbers = [1, 2, 3, 4, 5]
squared = numbers.map(&:square)
puts squared.inspect  # [1, 4, 9, 16, 25]

# 将Proc转换为块
my_proc = Proc.new { |n| n * 2 }
result = [1, 2, 3].map(&my_proc)
puts result.inspect  # [2, 4, 6]

🎯 模块(Module)

什么是模块?

模块是Ruby中用于组织代码的容器,它可以包含方法、常量和类。模块不能被实例化,但可以被包含到类中或作为命名空间使用。

ruby
# 定义模块
module MathUtils
  PI = 3.14159
  
  def self.square(n)
    n * n
  end
  
  def cube(n)
    n * n * n
  end
end

# 使用模块方法
puts MathUtils::PI      # 3.14159
puts MathUtils.square(5) # 25

模块作为命名空间

ruby
# 使用模块组织相关功能
module Database
  class Connection
    def initialize(host, port)
      @host = host
      @port = port
    end
  end
  
  class Query
    def initialize(sql)
      @sql = sql
    end
  end
  
  def self.connect(host, port)
    Connection.new(host, port)
  end
end

# 使用命名空间
connection = Database::Connection.new("localhost", 5432)
query = Database::Query.new("SELECT * FROM users")
db_connection = Database.connect("localhost", 5432)

Mixin(混入)

ruby
# 定义可混入的模块
module Comparable
  def >(other)
    compareTo(other) > 0
  end
  
  def <(other)
    compareTo(other) < 0
  end
  
  def ==(other)
    compareTo(other) == 0
  end
end

# 定义支持比较的类
class Person
  include Comparable  # 混入模块
  
  attr_reader :name, :age
  
  def initialize(name, age)
    @name = name
    @age = age
  end
  
  def compareTo(other)
    @age <=> other.age
  end
  
  def to_s
    "#{@name}(#{@age}岁)"
  end
end

# 使用混入的功能
person1 = Person.new("张三", 25)
person2 = Person.new("李四", 30)

puts person1 < person2  # true
puts person1 > person2  # false
puts person1 == person2 # false

extend和include的区别

ruby
module MyModule
  def instance_method
    "实例方法"
  end
  
  def self.class_method
    "类方法"
  end
end

class MyClass
  include MyModule  # 包含模块,添加实例方法
end

class AnotherClass
  extend MyModule   # 扩展模块,添加类方法
end

obj = MyClass.new
puts obj.instance_method  # 实例方法
# puts obj.class_method   # 错误

puts AnotherClass.instance_method  # 实例方法(作为类方法)
# puts AnotherClass.class_method   # 错误

模块方法

ruby
# 定义模块方法的不同方式
module MyModule
  # 方式1: 使用def self.method_name
  def self.module_method1
    "模块方法1"
  end
  
  # 方式2: 使用module_function
  def utility_method
    "工具方法"
  end
  module_function :utility_method
  
  # 方式3: 在模块内部定义类方法
  class << self
    def module_method2
      "模块方法2"
    end
  end
end

# 使用模块方法
puts MyModule.module_method1    # 模块方法1
puts MyModule.utility_method    # 工具方法
puts MyModule.module_method2    # 模块方法2

# 混入后也可以使用module_function定义的方法
class MyClass
  include MyModule
end

obj = MyClass.new
puts obj.utility_method  # 工具方法

🔄 模块高级特性

模块包含链

ruby
module A
  def method_a
    "A的方法"
  end
end

module B
  def method_b
    "B的方法"
  end
end

class MyClass
  include A
  include B
end

obj = MyClass.new
puts obj.method_a  # A的方法
puts obj.method_b  # B的方法

# 查看包含链
puts MyClass.ancestors.inspect
# [MyClass, B, A, Object, Kernel, BasicObject]

模块预包含

ruby
# 使用prepend改变方法查找顺序
module Logging
  def greet
    puts "开始记录"
    result = super  # 调用原始方法
    puts "记录完成"
    result
  end
end

class Person
  prepend Logging  # 预包含模块
  
  def greet
    "你好!"
  end
end

person = Person.new
puts person.greet
# 输出:
# 开始记录
# 你好!
# 记录完成

模块常量

ruby
module Constants
  PI = 3.14159
  VERSION = "1.0.0"
  
  class Configuration
    DEFAULT_TIMEOUT = 30
  end
end

# 访问模块常量
puts Constants::PI                    # 3.14159
puts Constants::VERSION               # 1.0.0
puts Constants::Configuration::DEFAULT_TIMEOUT  # 30

# 常量查找
module Outer
  CONST = "外部常量"
  
  module Inner
    CONST = "内部常量"
    
    def self.show_constants
      puts CONST          # 内部常量
      puts ::Outer::CONST # 外部常量
    end
  end
end

Outer::Inner.show_constants

🧪 块和模块实践示例

DSL(领域特定语言)构建器

ruby
module DSLBuilder
  def self.included(base)
    base.extend(ClassMethods)
  end
  
  module ClassMethods
    def build(&block)
      instance = new
      instance.instance_eval(&block) if block_given?
      instance
    end
  end
  
  def method_missing(method_name, *args, &block)
    if method_name.to_s.end_with?('=')
      # setter方法
      instance_variable_set("@#{method_name.to_s.chomp('=')}", args.first)
    else
      # getter方法
      instance_variable_get("@#{method_name}")
    end
  end
  
  def respond_to_missing?(method_name, include_private = false)
    true
  end
end

class Person
  include DSLBuilder
  
  def initialize
    @name = nil
    @age = nil
    @email = nil
  end
  
  def to_s
    "姓名: #{@name}, 年龄: #{@age}, 邮箱: #{@email}"
  end
end

# 使用DSL构建器
person = Person.build do
  name = "张三"
  age = 25
  email = "zhangsan@example.com"
end

puts person  # 姓名: 张三, 年龄: 25, 邮箱: zhangsan@example.com

配置管理模块

ruby
module Configurable
  def self.included(base)
    base.extend(ClassMethods)
  end
  
  module ClassMethods
    def config(&block)
      @config ||= {}
      if block_given?
        block.call(@config)
      else
        @config
      end
    end
  end
  
  def config
    self.class.config
  end
end

class Application
  include Configurable
  
  config do |c|
    c[:database_url] = "postgresql://localhost/myapp"
    c[:port] = 3000
    c[:debug] = true
  end
  
  def self.start
    puts "启动应用..."
    puts "数据库: #{config[:database_url]}"
    puts "端口: #{config[:port]}"
    puts "调试模式: #{config[:debug] ? '开启' : '关闭'}"
  end
end

Application.start
# 输出:
# 启动应用...
# 数据库: postgresql://localhost/myapp
# 端口: 3000
# 调试模式: 开启

测试框架模拟

ruby
module TestFramework
  def self.included(base)
    base.extend(ClassMethods)
  end
  
  module ClassMethods
    def test(name, &block)
      @tests ||= []
      @tests << {name: name, block: block}
    end
    
    def run_tests
      @tests ||= []
      puts "运行 #{@tests.length} 个测试..."
      
      @tests.each do |test|
        print "测试 '#{test[:name]}': "
        begin
          test[:block].call
          puts "通过"
        rescue => e
          puts "失败 - #{e.message}"
        end
      end
    end
  end
end

class MyTests
  include TestFramework
  
  test "加法测试" do
    result = 2 + 3
    raise "期望5,得到#{result}" unless result == 5
  end
  
  test "字符串测试" do
    str = "hello"
    raise "期望长度5,得到#{str.length}" unless str.length == 5
  end
  
  test "失败测试" do
    raise "这是一个故意的失败"
  end
end

MyTests.run_tests

🎯 块和模块最佳实践

1. 块使用最佳实践

ruby
# 好的做法:提供有用的块参数
def with_timer
  start_time = Time.now
  result = yield(start_time) if block_given?
  end_time = Time.now
  puts "执行时间: #{end_time - start_time}秒"
  result
end

with_timer { |start| sleep(1); "完成于#{start}" }

# 好的做法:检查块是否存在
def optional_block
  puts "开始处理"
  yield if block_given?
  puts "处理完成"
end

optional_block  # 不传递块也可以正常工作
optional_block { puts "块中的处理" }

# 避免:在循环中重复调用block_given?
# 不推荐:
def bad_example
  1000.times do |i|
    yield(i) if block_given?  # 每次都检查
  end
end

# 推荐:
def good_example(&block)
  return unless block
  1000.times do |i|
    block.call(i)
  end
end

2. 模块设计最佳实践

ruby
# 好的做法:单一职责的模块
module Authentication
  def authenticate(username, password)
    # 认证逻辑
  end
end

module Authorization
  def authorize(user, action)
    # 授权逻辑
  end
end

class UserController
  include Authentication
  include Authorization
end

# 好的做法:使用模块组织相关常量
module HttpStatus
  OK = 200
  NOT_FOUND = 404
  SERVER_ERROR = 500
  
  def self.message(code)
    case code
    when OK then "OK"
    when NOT_FOUND then "Not Found"
    when SERVER_ERROR then "Internal Server Error"
    else "Unknown"
    end
  end
end

puts HttpStatus.message(HttpStatus.OK)  # OK

3. Mixin最佳实践

ruby
# 好的做法:定义清晰的接口
module Timestampable
  def created_at
    @created_at ||= Time.now
  end
  
  def updated_at
    @updated_at ||= Time.now
  end
  
  def touch
    @updated_at = Time.now
  end
end

class Post
  include Timestampable
  
  def initialize(title)
    @title = title
  end
end

post = Post.new("我的文章")
puts post.created_at
post.touch
puts post.updated_at

4. 命名空间最佳实践

ruby
# 好的做法:使用嵌套模块组织大型项目
module MyApp
  module Models
    class User
      # 用户模型
    end
    
    class Post
      # 文章模型
    end
  end
  
  module Controllers
    class UsersController
      # 用户控制器
    end
    
    class PostsController
      # 文章控制器
    end
  end
  
  module Services
    class EmailService
      # 邮件服务
    end
    
    class PaymentService
      # 支付服务
    end
  end
end

# 使用完整命名空间
user = MyApp::Models::User.new
controller = MyApp::Controllers::UsersController.new

📚 下一步学习

掌握了Ruby块和模块后,建议继续学习:

继续您的Ruby学习之旅吧!

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