Ruby中的循环依赖关系

时间:2008-12-28 13:49:05

标签: ruby require circular-dependency

假设我们有两个类,Foo和Foo Sub,每个类分别位于不同的文件foo.rb和foo_sub.rb中。

foo.rb:

require "foo_sub"
class Foo
    def foo
        FooSub.SOME_CONSTANT
    end
end

foo_sub.rb:

require "foo"
class FooSub < Foo
    SOME_CONSTANT = 1
end

由于循环依赖性,这不起作用 - 我们无法定义任何一个没有另一个的类。我见过各种各样的解决方案。其中两个我想避免 - 即将它们放在同一个文件中并删除循环依赖。所以,我发现的唯一其他解决方案是前瞻声明:

foo.rb:

class Foo
end
require "foo_sub"
class Foo
    def foo
        FooSub.SOME_CONSTANT
    end
end

foo_sub.rb

require "foo"
class FooSub < Foo
    SOME_CONSTANT = 1
end

不幸的是,如果我有三个文件,我就无法做同样的工作:

foo.rb:

class Foo
end
require "foo_sub_sub"
class Foo
    def foo
        FooSubSub.SOME_CONSTANT
    end
end

foo_sub.rb:

require "foo"
class FooSub < Foo
end

foo_sub_sub.rb:

require "foo_sub"
class FooSubSub < FooSub
    SOME_CONSTANT = 1
end

如果我需要foo_sub.rb,那么FooSub是foo_sub_sub.rb中未初始化的常量。任何想法如何解决这个问题,而不是将它们放在同一个文件中,也不删除循环依赖?

3 个答案:

答案 0 :(得分:15)

如果您需要从超类访问子类,那么您的模型很可能会被破坏(即它应该是一个类)。

也就是说,有几个明显的解决方案:

1)只需创建一个需要foo文件的文件:

all_foos.rb:

require "foo.rb"
require "foo_sub.rb"

并从foo.rb和foo_sub.rb中删除要求。

2)从foo.rb

中删除require

3)从foo_sub.rb中删除require,并在类定义后将require放在foo.rb中。

Ruby不是C ++,在你调用Foo#foo()之前不会抱怨FooSub.SOME_CONSTANT;)

答案 1 :(得分:4)

另一个不错的选择是使用Ruby的自动加载功能。

它的工作原理如下:

 module MyModule
      autoload :Class1, File.join(File.dirname(__FILE__), *%w[my_module class1.rb])
      autoload :Class2, File.join(File.dirname(__FILE__), *%w[my_module class2.rb])
      # Code for MyModule here
 end

并且在这里描述得很好:

http://talklikeaduck.denhaven2.com/2009/04/06/all-that-you-might-require

答案 2 :(得分:3)

Sandi Metz解释了这个问题的一个解决方案,以及如何在她的Ruby实用面向对象设计(POODR)一书中很好地解决这个问题。

她的建议(我倾向于同意,因为它迄今为止对我来说是最好的),是将子类FooSub注入大师班Foo

这将在foo.rb中完成:

1   class Foo
2     def initialize(foo_sub:)
3     end
4   end

为了维护干净的代码,并使其易于更改,您可以将foo_sub包装在一个包装器方法中,这样您的类现在看起来像这样:

1   class Foo
2
3     attr_reader :foo_sub
4
5     def initialize(foo_sub:)
6       @foo_sub = foo_sub
7     end
8   end

(这里,attr_reader正在设置一个名为foo_sub的方法,然后传递给initialize哈希值的任何内容都是foo_sub的一个实例,因此@foo_sub(第6行) ),可以设置为方法foo_sub)的值。

现在,您可以让FooSub课程没有要求,使其独立于任何内容:

1   class FooSub
2     SOME_CONSTANT = 1
3   end

您可以向有权访问#SOME_CONSTANT的Foo类添加方法:

1   class Foo
2
3     attr_reader :foo_sub
4
5     def initialize(foo_sub:)
6       @foo_sub = foo_sub
7     end
8     
9     def foo
10      foo_sub.SOME_CONSTANT
11    end
12  end

实际上,通过这种方式,您将设置一个方法,该方法返回foo_sub @foo_sub的实例(在初始化时注入),并附加方法#SOME_CONSTANT。您的类只是期望初始化中注入的任何内容都响应#SOME_CONSTANT。因此,当它在REPL中设置FooSub时(例如IRB或PRY),你必须注入Foo类:

PRY
[1]>   require 'foo'
[2]>   => true
[3]>   require 'foo_sub'
[4]>   => true
[5]>   foo_sub = FooSub.new
[6]>   => #<FooSub:0x007feb91157140>
[7]>   foo = Foo.new(foo_sub: foo_sub)
[8]>   => #<Foo:0x007feb91157735 @foo_sub=FooSub:0x007feb91157140>
[9]>   foo.foo
[10]>  => 1

但是,如果你注入其他东西,你最终会得到:

PRY
[1]>   require 'foo'
[2]>   => true
[3]>   require 'foo_sub'
[4]>   => true
[5]>   foo_sub = FooSub.new
[6]>   => #<FooSub:0x007feb91157140>
[7]>   foo = Foo.new(foo_sub: 'something else as a string')
[8]>   => #<Foo:0x007feb91157735 @foo_sub='something else as a string'>
[9]>   foo.foo
[10]>  => UNDEFINED CONSTANT #SOME_CONSTANT ERROR MESSAGE

我不知道实际的错误消息会在第10行读到什么,但请按照这些方式进行思考。发生此错误是因为您已经有效地尝试在字符串'其他字符串'或'something else as a string'.SOME_CONSTANT上运行方法#SOME_CONSTANT,这显然不起作用。