Skip to content

Ruby 模块

Ruby 模块:命名空间和 Mixins (混入)

Section titled “Ruby 模块:命名空间和 Mixins (混入)”

Ruby 中的模块主要有两个用途:

  • **命名空间(Namespacing):**它们提供了一种方式来对相关的方法、类和常量进行分组,防止代码库的不同部分或与第三方库之间发生名称冲突。
  • **Mixins(混入):**它们允许在多个类之间共享通用功能(实例方法),而无需使用传统的继承,提供了一种多重继承的形式。

模块使用 module 关键字定义,后跟模块名(按照约定,模块名应使用 CamelCase),以 end 结束。

module MyLogger
PI = 3.14159 # A constant within the module
# A module method (called on the module itself)
def self.log(message)
puts "[LOG] #{Time.now}: #{message}"
end
# An instance method (intended for mixing into classes)
def print_info(text)
"INFO: #{text}"
end
end

访问模块常量和方法:

模块常量使用 ::(作用域解析)运算符访问:MyLogger::PI。

模块方法(使用 self. 或 ModuleName. 定义)直接在模块上调用:MyLogger.log("Application started")。

Ruby require 和 require_relative 语句:

Section titled “Ruby require 和 require_relative 语句:”

要使用定义在单独文件中的模块,你需要加载该文件。Ruby 提供了两种主要方式来实现:

  • require 'filename': 加载一个文件。Ruby 会在 $LOAD_PATH(一个包含路径的数组)列出的目录中搜索该文件。它通常用于加载已安装的 Gem 或标准库功能。通常可以省略 .rb 扩展名。
  • require_relative 'filename': 加载相对于包含 require_relative 语句的文件路径的文件。这更适用于加载你自己的项目文件。通常包含 .rb 扩展名,或者如果很明确也可以省略。

假设有两个不同的库都定义了一个 Utility 模块。

payments_utility.rb
module Payments
module Utility
def self.format_currency(amount)
"$%.2f" % amount
end
end
end
# File: reporting_utility.rb
module Reporting
module Utility
def self.generate_report_name(base_name)
"#{base_name}_#{Time.now.strftime('%Y%m%d')}.pdf"
end
end
end
# File: main_app.rb
require_relative 'payments_utility'
require_relative 'reporting_utility'
puts Payments::Utility.format_currency(123.45) # Output: $123.45
puts Reporting::Utility.generate_report_name("sales") # Output: sales_20230315.pdf (date will vary)

这演示了模块如何防止名称冲突。Payments::Utility 与 Reporting::Utility 是不同的。

include 关键字用于将模块的实例方法和常量混入(mixin)到类中。当一个类 include 一个模块时,该模块的实例方法就会成为这个类的实例方法。

include 用于 Mixins 的示例:

printable.rb
module Printable
# This instance method will be mixed into any class that includes Printable
def pretty_print
"<#{self.class} - #{self.object_id}> Attributes: #{instance_variables.map { |var| "#{var}=#{instance_variable_get(var).inspect}" }.join(', ')}"
end
end
# File: book.rb
require_relative 'printable'
class Book
include Printable # Mixes in the pretty_print method from Printable module
attr_accessor :title, :author
def initialize(title, author)
@title = title
@author = author
end
end
class Magazine
include Printable
attr_accessor :name, :issue_date
def initialize(name, issue_date)
@name = name
@issue_date = issue_date
end
end
# File: app.rb
require_relative 'book'
require_relative 'magazine'
my_book = Book.new("The Great Gatsby", "F. Scott Fitzgerald")
my_magazine = Magazine.new("Tech Weekly", "2023-10-26")
puts my_book.pretty_print
# Example Output: <Book - 701...> Attributes: @title="The Great Gatsby", @author="F. Scott Fitzgerald"
puts my_magazine.pretty_print
# Example Output: <Magazine - 702...> Attributes: @name="Tech Weekly", @issue_date="2023-10-26"

在这个示例中,Book 和 Magazine 类都获得了 pretty_print 功能,而无需为了这个特定行为而继承一个共同的超类。这就是 Mixins 的强大之处。

被 include 的模块中的常量也可以直接在类中访问(例如,如果 Printable 有一个常量 VERSION,Book 可以通过 VERSION 访问它)。

include 将模块的方法添加为实例方法,而 extend 将它们添加为类的类方法。

class_utils.rb
module ClassUtils
def find(id)
# Hypothetical find logic for a class method
puts "#{self.name} finding record with id: #{id}"
# new(id: id) # for example
end
end
# File: user.rb
require_relative 'class_utils'
class User
extend ClassUtils # Adds ClassUtils methods as class methods to User
attr_accessor :id, :name
def initialize(id: nil, name: nil)
@id = id
@name = name
end
end
# File: app.rb
require_relative 'user'
User.find(101) # Calls the 'find' method as a class method on User
# Output: User finding record with id: 101

prepend 在 Ruby 2.0 中引入,类似于 include,但它将模块插入到继承链中,位于类本身之前。这意味着被 prepend 的模块中的方法可以覆盖类中的方法,并且可以使用 super 调用类原有的方法。这是一个更高级的特性,常用于方法装饰或以比 include 更深入的方式修改行为。

module LoggingPrepender
def save
puts "[PREPEND LOG] Before saving #{self.class.name}..."
result = super # Calls the original 'save' method of the class
puts "[PREPEND LOG] After saving #{self.class.name}... Result: #{result}"
result
end
end
class Document
prepend LoggingPrepender
def save
puts "Document saving..."
true # Simulate successful save
end
end
doc = Document.new
doc.save
# Output:
# [PREPEND LOG] Before saving Document...
# Document saving...
# [PREPEND LOG] After saving Document... Result: true

实际应用:

模块在 Ruby 中被广泛使用。整个 Ruby 标准库都组织在模块中(例如 Math, File, Enumerable)。Rails 框架组件如 ActiveRecord::Base 或 ActionController::Base 包含了许多模块来提供其丰富的功能集。你会使用模块来组织自己的代码、共享工具函数以及实现横切关注点(cross-cutting concerns)如日志或序列化。