OO design: avoiding an infinite loop in classes initializing each other?

I have two classes, each of which use methods from the other class. But
because they initialize an instance of each other, this creates an
infinite loop!

I would like to keep them separate and not merge them. What’s the best
/ most elegant / proper way of solving this problem? There is probably
something simple that I’m just not seeing.

Simplified code:

class A
def initialize
b = B.new
end

def method_foo
# do something with b
end
end

class B
def initialize
a = A.new
end

def method_bar
# do something with a
end
end

Any help gratefully received!

On 09/01/2013 10:53 AM, Andrew S. wrote:

class A
def initialize
a = A.new
end

def method_bar
# do something with a
end
end

Any help gratefully received!

class A
def initialize b = nil
@b = b || B.new(self)
end
end

class B
def initialize a = nil
@a = a || A.new(self)
end
end

a = A.new

p a # #<A:0x21dbf9e0 @b=#<B:0x21dbf9cc @a=#<A:0x21dbf9e0 …>>>

Hi,

Thanks for replying! Well, I’ve tested it and it works (see extended
example below). But I don’t know why it works!

Why set the arguments to nil? Why does @b = b || B.new(self) work?

Extended example:

class A
def initialize b = nil
@b = b || B.new(self)
end

def do_something_with_b
@b.say_bar
end

def say_foo
puts “foo”
end
end

class B
def initialize a = nil
@a = a || A.new(self)
end

def do_something_with_a
@a.say_foo
end

def say_bar
puts “bar”
end
end

a = A.new
b = B.new

b.do_something_with_a # => foo
a.do_something_with_b # => bar

I think I understand. Thank you very much!

On 09/01/2013 03:25 PM, Andrew S. wrote:

Hi,

Thanks for replying! Well, I’ve tested it and it works (see extended
example below). But I don’t know why it works!

Why set the arguments to nil? Why does @b = b || B.new(self) work?

The b = nil means that b is optional. If it is not passed by the
caller (as in: A.new), then the initial value of b is nil. If an
argument is provided by the caller (as in: A.new(self)), then that
value is assigned to b.

In those two cases, this line

   @b = b || B.new(self)

will behave differently.

If b was passed in as an argument to A.new, then the effect is

   @b = b

If not, then the effect is

   @b = B.new(self)

Having these two cases is what makes it possible to set up the circular
reference that you wanted. When you construct an A like so:

   a = A.new

there is no argument. So A.new calls B.new. The trick is that it calls
B.new with an argument: B.new(self). Since an argument is passed to
B.new, it simply assigns that argument (an instance of A) to its @a
attribute.

Hope that helps…

On 2013-09-02, at 12:31 AM, Joel VanderWerf [email protected]
wrote:

In those two cases, this line

 @b = B.new(self)

Having these two cases is what makes it possible to set up the circular
reference that you wanted. When you construct an A like so:

 a = A.new

there is no argument. So A.new calls B.new. The trick is that it calls B.new
with an argument: B.new(self). Since an argument is passed to B.new, it simply
assigns that argument (an instance of A) to its @a attribute.

Hope that helps…

Why not let Ruby do the defaulting for you? For example:

#!/usr/bin/env ruby

class A
def initialize(b = B.new(self))
@b = b
end

def method_foo

do something with b

end
end

class B
def initialize(a = A.new(self))
@a = a
end

def method_bar

do something with a

end
end

a = A.new
p a
b = B.new
p b

END

produces the right results.

ratdog:tmp mike$ ./try.rb
#<A:0x007fafc4128b50 @b=#<B:0x007fafc4128b28 @a=#<A:0x007fafc4128b50
…>>>
#<B:0x007fafc4128970 @a=#<A:0x007fafc4128948 @b=#<B:0x007fafc4128970
…>>>

Is this bad form?

Mike

Mike S. [email protected]
http://www.stok.ca/~mike/

The “`Stok’ disclaimers” apply.

On Sep 1, 2013, at 12:53 PM, Andrew S. [email protected] wrote:

I have two classes, each of which use methods from the other class. But
because they initialize an instance of each other, this creates an
infinite loop!

I would like to keep them separate and not merge them. What’s the best
/ most elegant / proper way of solving this problem? There is probably
something simple that I’m just not seeing.

I obviously don’t know the specifics of what you’re doing, but I have to
say the general pattern has a distinct odour to it. This smacks of
too-tightly coupled classes, and probably low cohesion. I’d seriously
consider refactoring this, and thinking it out a bit further so your
classes are more independent.

On 09/02/2013 08:37 AM, Mike S. wrote:

Why not let Ruby do the defaulting for you? For example:

#!/usr/bin/env ruby

class A
def initialize(b = B.new(self))
@b = b
end

produces the right results.

Even better! However, it is More Magic…

On Mon, Sep 2, 2013 at 8:14 PM, Tamara T. [email protected]
wrote:

I obviously don’t know the specifics of what you’re doing, but I have to say the
general pattern has a distinct odour to it. This smacks of too-tightly coupled
classes, and probably low cohesion. I’d seriously consider refactoring this, and
thinking it out a bit further so your classes are more independent.

… or at least do not employ such a circular dependency for
construction.

Kind regards

robert

On 09/02/2013 11:14 AM, Tamara T. wrote:

I obviously don’t know the specifics of what you’re doing, but I have to say the
general pattern has a distinct odour to it. This smacks of too-tightly coupled
classes, and probably low cohesion. I’d seriously consider refactoring this, and
thinking it out a bit further so your classes are more independent.

A little “dependency injection” to decouple your classes…

class A
attr_reader :container

def initialize container
@container = container
end

def b
container.b
end

def do_something_with_b
b.say_bar
end

def say_foo
puts “foo”
end
end

class B
attr_reader :container

def initialize container
@container = container
end

def a
container.a
end

def do_something_with_a
a.say_foo
end

def say_bar
puts “bar”
end
end

class MyContainer
def a
@a ||= A.new(self)
end

def b
@b ||= B.new(self)
end
end

mc = MyContainer.new

mc.a.do_something_with_b # ==> bar
mc.b.do_something_with_a # ==> foo