假设我们有两个类,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的自动加载功能。
它的工作原理如下:
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实用面向对象设计(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,这显然不起作用。