Ruby中的循环依赖关系

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

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

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

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;)

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

它的工作原理如下:

  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

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

她建议的内容(我倾向于同意,因为它对我来说效果最好),就是将子类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中设置Foo时注入FooSub类(例如IRB或PRY):

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

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

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

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