Circular Dependencies in Ruby

前端 未结 3 1597
执笔经年
执笔经年 2020-12-17 17:28

Let\'s say we have two classes, Foo and Foo Sub, each in a different file, foo.rb and foo_sub.rb respectively.

foo.rb:

require \"foo_sub\"
class Foo
         


        
3条回答
  •  伪装坚强ぢ
    2020-12-17 18:04

    Sandi Metz explains one solution to this problem and how to solve it really nicely in her book Practical Object-Oriented Design in Ruby (POODR).

    What she suggests (and I'm inclined to agree with as its worked the best for me so far), is to inject the sub-class FooSub into the master class Foo.

    This would be done in foo.rb with:

    1   class Foo
    2     def initialize(foo_sub:)
    3     end
    4   end
    

    to maintain clean code, and keep it easily changeable, you would then wrap the foo_sub in a wrapper method so your class now looks like this:

    1   class Foo
    2
    3     attr_reader :foo_sub
    4
    5     def initialize(foo_sub:)
    6       @foo_sub = foo_sub
    7     end
    8   end
    

    (here, the attr_reader is setting up a method called foo_sub and then whatever is passed into the value of the initialize hash is an instance of foo_sub, therefore @foo_sub (line 6), can be set to the value of the method foo_sub).

    You can now have your FooSub class with no requires, making it independent of anything:

    1   class FooSub
    2     SOME_CONSTANT = 1
    3   end
    

    and you can add a method to your Foo class that has access to #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
    

    In actuality, with this, you're setting up a method that returns the instance of foo_sub @foo_sub (that is injected at the initialize), with the method #SOME_CONSTANT appended onto it. Your class just expects whatever is injected in at the initialize to respond to #SOME_CONSTANT. SO for it to work you would have to inject you FooSub class when setting up Foo in a REPL (e.g IRB or 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
    

    if, however, you injected something else, you'd end up with:

    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
    

    I don't know what the actual error message would read on line 10 but think along those lines. This error would of occurred because you'd have effectively tried to run the method #SOME_CONSTANT on the string 'something else as a string' or 'something else as a string'.SOME_CONSTANT which would obviously not work.

提交回复
热议问题