ruby:如何正确地要求(避免循环依赖)
今天我面临一个奇怪的问题: 模块上出现“缺少方法”错误,但该方法存在,并且需要定义模块的文件。经过一些搜索之后,我发现了一个循环依赖项,其中两个文件相互需要,现在我假设ruby以静默方式中止循环需要ruby:如何正确地要求(避免循环依赖),ruby,dependencies,dependency-management,circular-dependency,Ruby,Dependencies,Dependency Management,Circular Dependency,今天我面临一个奇怪的问题: 模块上出现“缺少方法”错误,但该方法存在,并且需要定义模块的文件。经过一些搜索之后,我发现了一个循环依赖项,其中两个文件相互需要,现在我假设ruby以静默方式中止循环需要 编辑开始:示例 文件“a.rb”: require './b.rb' module A def self.do_something puts 'doing..' end end 文件“b.rb”: require './a.rb' module B d
编辑开始:示例 文件“a.rb”:
require './b.rb'
module A
def self.do_something
puts 'doing..'
end
end
文件“b.rb”:
require './a.rb'
module B
def self.calling
::A.do_something
end
end
B.calling
执行b.rb会在“调用”中给出b.rb:5:未初始化的常量A(namererror)
。这两个文件都必须有requires,因为它们要从命令行独立运行(我编写了这段代码以保持简短)。
所以B.电话必须在那里。一个可能的解决方案是将requires包装到if uuu FILE\uuuu==0
,但这似乎不是正确的方法
编辑结束
为了避免这些难以发现的错误(顺便问一下,如果require抛出一个异常不是更好吗?),是否有一些关于如何构建项目以及在哪里需要什么的指导方针/规则?例如,如果我有
module MainModule
module SubModule
module SubSubModule
end
end
end
在哪里需要子模块?全部在主目录中,还是只有主目录中的子目录和子目录中的子目录
任何帮助都会很好
总结
ForFS的回答和评论中讨论了为什么会发生这种情况
到目前为止,最佳实践(如lain所指出或暗示的)似乎如下(如果我错了,请纠正我):
main模块
需要子模块
,而子模块
需要子模块
)感谢所有回答/评论的人,这对我帮助很大!不久前,我在Ruby邮件列表中询问了这个问题,当时我的库中有一个文件只是为了需要一些东西,我改变了以下两条规则:
require\u relative
require
B.calling
。如果从B.rb中删除B.calling,则可以正常工作。例如,在irb中,在代码文件中不调用B.calling,但随后运行:
$irb需要“/Volumes/RubyProjects/Test/stackoverflow8057625/b.rb”
=>正确
B.呼叫
正在做..
=>零
希望您已经知道的几件基本事情:
require
仅将文件中的代码插入程序的该点,换句话说,程序顶部的require
将在底部的require
之前解释ruby a.rb
这是ruby解释器将看到并执行的内容:
#load file b.rb <- from require './b.rb' in 'a.rb' file
#load file a.rb <- from require './a.rb' in 'b.rb' file
#this runs because a.rb has not yet been required
#second attempt to load b.rb but is ignored <- from require './b.rb' in 'a.rb' file
#finish loading the rest of a.rb
module A
def self.do_something
puts 'doing..'
end
end
#finish loading the rest of b.rb
module B
def self.calling
::A.do_something
end
end
B.calling
#Works because everything is defined
希望这能解释其他人给你的好答案,如果你仔细想想,为什么很难回答你的最后一个问题:在哪里放置require语句。在Ruby中,你需要的是文件而不是模块,因此在代码中放置require的位置取决于文件的组织方式
如果您绝对需要能够以随机顺序定义模块和执行方法,那么您可以实现类似的功能来收集对尚不存在的模块的调用,然后在它们出现时调用它们
module Delay
@@q = {}
def self.call_mod(*args) #args format is method_name, mod_name, *args
mod_name = args.shift
method_name = args.shift
#remaining args are still in args
mod = Object.const_get(mod_name.to_sym)
mod.send(method_name.to_sym, *args)
end
def self.exec(mod_name, *args)
begin
args.unshift(mod_name)
self.call_mod(*args)
rescue NameError, NoMethodError
@@q[mod_name] ||= []
@@q[mod_name] << args
end
end
def self.included(mod)
#get queued methods
q_list = @@q[mod.name.to_sym]
return unless q_list
#execute delayed methods now that module exists
q_list.each do |args|
self.call_mod(*args)
end
end
end
结果:
#=> doing..
最后一步是将代码分解成文件
delay.rb #holds just Delay module
a.rb #holds the A module and any calls to other modules
b.rb #holds the B module and any calls to other modules
只要您确保模块文件(a.rb和b.rb)的第一行是require'delay'
,并且模块末尾包含delay,一切都应该正常
最后一点注意:只有当您无法将定义代码与模块执行调用解耦时,此实现才有意义。此外,请注意(与
加载不同)
)多个requires
到同一个文件只会导致一次加载该文件。感谢您的回答。但是,正如您可以尝试使用我添加的示例一样,循环requires存在一些问题。是的,这是可行的,但是这些文件应该可以自己运行,因此B.calling
必须在那里(请参见编辑)。或者这只是一个非常糟糕的想法,只会带来麻烦吗?正如@sheldonh在上面所说的,现在代码过于简单,如果我必须对其进行评论,我只会说不要这样编写。如果两个文件都需要单独运行,但共享行为,那么我建议采用该共享行为,并将其放在第三个文件中,这两个文件都需要因此,倾向于将“库”类型代码(不独立运行的东西)与“可运行的”类型代码(独立运行的东西)分开,这样B.calling就会在它自己的文件中
#=> doing..
delay.rb #holds just Delay module
a.rb #holds the A module and any calls to other modules
b.rb #holds the B module and any calls to other modules