Круговые зависимости в Ruby - PullRequest
11 голосов
/ 28 декабря 2008

Скажем, у нас есть два класса, 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. Любые идеи, как обойти это, не помещая их в тот же файл, ни удаляя циклическую зависимость?

Ответы [ 3 ]

15 голосов
/ 28 декабря 2008

Если вам нужен доступ к подклассу из суперкласса, то есть большая вероятность, что ваша модель сломана (то есть это должен быть один класс).

Тем не менее, есть несколько очевидных решений:

1) просто создайте файл, который требует файлы foo:

all_foos.rb:

require "foo.rb"
require "foo_sub.rb"

и удалите требуемые из foo.rb и foo_sub.rb.

2) убрать требование из foo.rb

3) удалите require из foo_sub.rb и поместите require в foo.rb после определения класса.

Ruby не C ++, он не будет жаловаться на FooSub.SOME_CONSTANT, пока вы не вызовете Foo # foo ();)

4 голосов
/ 10 октября 2009

Другой достойный вариант - использовать функцию автозагрузки 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 голосов
/ 28 мая 2016

Сэнди Метц рассказывает об одном решении этой проблемы и о том, как действительно решить ее, в своей книге «Практический объектно-ориентированный дизайн в 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. Поэтому, чтобы это работало, вам нужно ввести класс FooSub при настройке Foo в REPL (например, IRB или PRY):

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, которая, очевидно, не будет работать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...