假设我们有两个类,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中未初始化的常量。任何想法如何解决这个问题,而不是将它们放在同一个文件中,也不删除循环依赖?
答案 0 :(得分:15)
如果您需要从超类访问子类,那么您的模型很可能会被破坏(即它应该是一个类)。
也就是说,有几个明显的解决方案:
1)只需创建一个需要foo文件的文件:
all_foos.rb:
require "foo.rb"
require "foo_sub.rb"
并从foo.rb和foo_sub.rb中删除要求。
2)从foo.rb
中删除require3)从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,这显然不起作用。