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 # falseextend和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
end2. 模块设计最佳实践
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) # OK3. 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_at4. 命名空间最佳实践
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 类和对象 - 深入学习面向对象编程
- Ruby 继承和多态 - 掌握面向对象高级特性
- Ruby 元编程 - 学习动态代码生成
- Ruby 设计模式 - 了解常见的设计模式应用
继续您的Ruby学习之旅吧!