Ruby中的循环依赖关系

问题描述 投票:11回答:3

假设我们有两个类,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中的未初始化常量。任何想法如何解决这个问题,而不是将它们放在同一个文件中,也不删除循环依赖?

ruby require circular-dependency
3个回答
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;)


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


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 hash的值的是foo_sub的一个实例,因此@foo_sub(第6行)可以设置为方法foo_sub的值)。

你现在可以让你的FooSub课没有要求,使它独立于任何东西:

1   class FooSub
2     SOME_CONSTANT = 1
3   end

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

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(例如IRB或PRY)中设置FooSub时,你必须注入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,这显然不起作用。

© www.soinside.com 2019 - 2024. All rights reserved.