use*_*335 14 ruby dependencies circular-dependency dependency-management
今天我遇到了一个奇怪的问题:模块上出现"遗漏方法"错误,但方法就在那里,并且需要定义模块的文件.经过一番搜索,我找到了一个循环依赖,其中2个文件需要彼此,现在我认为ruby默默地中止循环要求.
编辑开始:示例
文件'a.rb':
require './b.rb'
module A
def self.do_something
puts 'doing..'
end
end
Run Code Online (Sandbox Code Playgroud)
文件'b.rb':
require './a.rb'
module B
def self.calling
::A.do_something
end
end
B.calling
Run Code Online (Sandbox Code Playgroud)
执行b.rb给出b.rb:5:in 'calling': uninitialized constant A (NameError)
.对于两个文件都必须存在这些要求,因为它们要从命令行自行运行(我省略了该代码以保持简短).所以B.calling必须在那里.一种可能的解决方案是将需求包装起来if __FILE__ == $0
,但这似乎不是正确的方法.
编辑结束
避免这些难以发现的错误(顺便说一下,如果要求抛出异常会不会更好?),是否有一些关于如何构建项目的指导方针/规则以及在哪里需要什么?例如,如果我有
module MainModule
module SubModule
module SubSubModule
end
end
end
Run Code Online (Sandbox Code Playgroud)
我应该在哪里需要子模块?所有在主要的,或只有主要的sub和sububub中的sub?
任何帮助都会很好.
在forforfs的回答和评论中讨论了为什么会发生这种情况的原因.
到目前为止,最佳实践(如lain指出或暗示)似乎如下(如果我错了,请纠正我):
MainModule
将需要SubModule
,Submodule
并将需要SubSubModule
)感谢所有回答/评论的人,这对我帮助很大!
iai*_*ain 10
在一段时间之后在Ruby邮件列表上询问这个问题后,当我以前在我的库中有一个文件只是为了要求时,我改为这两个规则:
如果一个文件需要来自同一个库中另一个的代码,我会require_relative
在需要代码的文件中使用.
如果文件需要来自不同库的代码,我会require
在需要代码的文件中使用.
据我所知,Ruby要求按顺序要求,因此循环依赖关系无关紧要.
(Ruby v1.9.2)
回答有关显示循环依赖问题的示例的注释:
实际上,示例的问题并不是需求是循环的,而是B.calling
在需求完成之前调用.如果从b.rb中删除B.calling,它可以正常工作.例如,在代码文件中没有B.calling但之后运行的irb中:
$ irb
require'/ volume@RubyProjects/Test/stackoverflow8057625/b.rb'
=> true
B.calling
doing ..
=> nil
你希望已经知道几件基本的事情:
Ruby是解释的,而不是编译的,所以你不能执行解释器没有看到的任何代码.
require
只需将文件中的代码插入到程序的那一点,换句话说,require
程序顶部的a将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
Run Code Online (Sandbox Code Playgroud)
如果您首先运行b ruby b.rb
,解释器会看到:
#load file a.rb <- from require './a.rb' in 'b.rb' file
#load file b.rb <- from require './b.rb' in 'a.rb' file
#this runs because b.rb has not yet been required
#second attempt to load a.rb but is ignored <- from require './a.rb' in 'b.rb' file
#finish loading the rest of b.rb
module B
def self.calling
::A.do_something
end
end
B.calling #NameError, ::A.do_something hasn't been defined yet.
Run Code Online (Sandbox Code Playgroud)
希望这能解释其他人给你的好答案,如果你想一想,为什么很难回答你关于在哪里提出需求陈述的最后一个问题.使用Ruby,您需要的是文件而不是模块,因此将代码放在代码中的位置取决于文件的组织方式.
如果你绝对需要能够定义模块并且方法以随机顺序执行,那么你可以实现类似的东西来收集对尚不存在的模块的调用,然后在它们出现时调用它们.
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
Run Code Online (Sandbox Code Playgroud)
一定要先定义延迟模块然后再调用B.calling
你要使用的Delay.exec(:B, :calling, any_other_args)
.所以,如果你在延迟模块之后有这个:
Delay.exec(:B, :calling) #Module B is not defined
module B
def self.calling
::A.do_something
end
include Delay #must be *after* any callable method defs
end
module A
def self.do_something
puts 'doing..'
end
include Delay #must be *after* any callable method defs
end
Run Code Online (Sandbox Code Playgroud)
结果是:
#=> doing..
Run Code Online (Sandbox Code Playgroud)
最后一步是将代码分解为文件.一种方法可能是拥有三个文件
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
Run Code Online (Sandbox Code Playgroud)
只要你确定require 'delay'
模块文件的第一行(a.rb和b.rb)和延迟包含在模块的末尾,事情应该有效.
最后注意:如果您无法将定义代码与模块执行调用分离,则此实现才有意义.
归档时间: |
|
查看次数: |
10249 次 |
最近记录: |