12

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

4

3 回答 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++,它不会抱怨 FooSub.SOME_CONSTANT 直到你调用 Foo#foo() ;)

于 2008-12-28T14:04:09.767 回答
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

于 2009-10-10T02:25:02.503 回答
4

Sandi Metz 在她的 Practical Object-Oriented Design in 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,然后传入初始化哈希值的任何内容都是 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 行的实际错误信息是什么,但按照这些思路思考。之所以会发生此错误,是因为您已经有效地尝试在字符串“其他字符串”上运行方法#SOME_CONSTANT,或者'something else as a string'.SOME_CONSTANT显然不起作用。

于 2016-05-28T13:21:25.133 回答