Ruby:能否在非类环境中使用include引入模块并调用其方法?
我有一个存储数据的JSON文件,一个包含加载音乐专辑方法的模块MusicAlbumModule,同目录下有MusicAlbum类文件。希望在main.rb中调用这些类,创建独立函数来调用模块方法,无需基于类的结构。已知在类中使用include引入模块可调用其方法,但不清楚非类场景下能否使用include,以及如何实现。
相关代码如下:
Music.rb
require_relative '../item' class MusicAlbum < Item attr_accessor :on_spotify, :name, :publish_date def initialize(name, publish_date, on_spotify) @id = Random.rand(1..100) super(publish_date) # Music calls all constructor item with super @name = name @on_spotify = on_spotify end def can_be_archived? super && @on_spotify end end
./modules/music_module.rb
require 'json' require_relative '../classes/music_album' module MusicAlbumModule def load_music_albums data = [] file = './json_files/music_album.json' if File.exist?(file) JSON.parse(File.read(file)).each do |music| data.push(MusicAlbum.new(music['name'], music['publish_date'], music['on_spotify'])) end else File.write('./json_files/music_album.json', []) end data end def create_music_album data = [] @music_albums.each do |album| data.push({ name: album.name, publish_date: album.publish_date, on_spotify: album.on_spotify }) end File.write('./json_files/music_album.json', JSON.generate(data)) end end
类场景下调用模块方法的示例:
def add_music_album puts 'Album name: ' name = gets.chomp puts 'Date of publish [Enter date in format (yyyy-mm-dd)]' publish_date = gets.chomp puts 'Is it available on Spotify? Y/N' on_spotify = gets.chomp.downcase case on_spotify when 'y' @music_albums.push(MusicAlbum.new(name, publish_date, true)) when 'n' @music_albums.push(MusicAlbum.new(name, publish_date, false)) end puts 'Music album created' end
好问题!在Ruby里,include确实是用来把模块混入类或其他模块的,没法直接在顶级作用域(也就是你说的非类场景)直接用,但有几种简单的方式可以实现你的需求,不用依赖类结构:
方法1:把模块方法改为模块级方法(推荐)
你可以给模块里的方法加上self.前缀,把它们变成模块的类方法(模块级方法),这样就可以直接通过模块名来调用,完全不需要类。
修改你的music_module.rb:
require 'json' require_relative '../classes/music_album' module MusicAlbumModule # 加上self.变成模块级方法 def self.load_music_albums data = [] file = './json_files/music_album.json' if File.exist?(file) JSON.parse(File.read(file)).each do |music| data.push(MusicAlbum.new(music['name'], music['publish_date'], music['on_spotify'])) end else # 注意:这里要写入空数组的JSON字符串,原代码的[]会被转成空字符串导致解析错误 File.write('./json_files/music_album.json', '[]') end data end # 把@music_albums改成参数,避免依赖实例变量,适配非类场景 def self.create_music_album(music_albums) data = music_albums.map do |album| { name: album.name, publish_date: album.publish_date, on_spotify: album.on_spotify } end File.write('./json_files/music_album.json', JSON.generate(data)) end end
然后在main.rb的独立函数里直接调用:
require_relative './modules/music_module' require_relative './classes/music_album' def load_albums # 直接通过模块名调用方法 albums = MusicAlbumModule.load_music_albums puts "Loaded #{albums.length} music albums" albums end def save_albums(albums) MusicAlbumModule.create_music_album(albums) puts "Albums saved successfully" end # 测试调用 my_albums = load_albums # 假设添加新专辑后保存 save_albums(my_albums)
这个方法的好处是语义清晰,明确这些方法属于MusicAlbumModule,不会污染顶级作用域,是最推荐的方式。
方法2:在顶级作用域extend模块
如果你不想修改原模块的代码,可以在main.rb里用extend把模块混入顶级对象(Ruby里顶级作用域的self是main对象),这样模块的实例方法就变成了main对象的方法,能直接调用。
在main.rb里这样写:
require_relative './modules/music_module' require_relative './classes/music_album' # 把模块的实例方法变成main对象的方法 extend MusicAlbumModule def load_albums # 直接调用模块的实例方法 albums = load_music_albums puts "Loaded #{albums.length} music albums" albums end # 原模块的create_music_album依赖@music_albums实例变量,需要在顶级作用域定义它 @music_albums = load_albums def save_albums # 直接调用模块的实例方法 create_music_album puts "Albums saved successfully" end save_albums
不过这种方法要注意:原模块的create_music_album依赖实例变量@music_albums,你需要确保这个变量在顶级作用域存在,或者像方法1那样修改模块把它改成参数(更灵活)。
为什么不能直接用include?
Ruby里include的作用是把模块的实例方法混入类或模块的实例方法列表,它只能在类或模块的定义内部使用,不能在顶级作用域直接调用。而extend是把模块的实例方法变成目标对象的方法,顶级作用域的self是main对象,所以用extend可以实现把模块方法引入顶级作用域。
内容的提问来源于stack exchange,提问作者Shedrack Sunday

