4

I'm designing an object dependency graph of my program and one ambiguity between design variants appears from time to time.

Imagine two objects having a reference to each other. Obviously, at least one reference should be assigned after object's initialization (for example, through a subscription method) and another one optionally can be a direct dependency.

Here's an object diagram

enter image description here

I'm not sure I've used correct UML, so here's my description:

1) Both objects have subscription/binding methods. Usage:

A a = new A();    
B b = new B();    
a.Set(b);    
b.Set(a);

2) ObjectB is a component of ObjectA:

A a = new A(new B());     

// somewhere, possibly in a's method :    
b.Set(a);

3) Third is actually an opposite of the second, no need to explain.

In my situation I can use any of these and my program will work, but I want some theoretical reasons. Can they be found?

astef
  • 338

2 Answers2

3

Disclaimer - I don't want to discuss if a cyclic reference is good or bad - IMHO there are situations where this kind of design is 100% appropriate, and for the sake of not starting a holy war, let's assume this is such a case here.

Your 3 alternatives don't differ much - at least, not in languages like C# or Java. What you get is two objects a and b, each holding a reference to each other. This is also true for (2) - if you see B as a component of A is more a question of your point of view (since classes are always reference types in these languages).

So the only difference between (2) and (1) is if you pass one of the two references within the constructor, or by using a separate Set method. Choose the "constructor" alternative if you want your cyclic references to be initialized immediately, after object construction, making sure they are as soon initialized as possible. Use the Set alternative if you need the option to delay the initialization (maybe you want to use single A objects without the need to provide always a B object - and vice versa - in some scenarios).

Final remark: it is much easier to make such a decision in terms of the context, knowing the names of the classes, the semantics, the context and the usage scenario.

Doc Brown
  • 218,378
2

As always, asking a question advances my own understanding of the issue.

The main difference between those three variants is object's lifetime. That what is absent when we are talking about classes, but it is very important in case of objects.

In first variant, lifetimes are independent. Both objects can live without each other. a's knowledge of b starts from receiving a message about it, the same as b's knowledge of a. The responsibility for connecting those object's lifetimes falls to a third object, so this variant must be used only when there is a real need to have a and b living without each other. That is rare, actually. I'm still searching for a good and simple example.

In the second variant a knows about b during all life. We say a owns b and use something like C#'s readonly modifier for a field with reference to b. But b can't own a, because it will be impossible to instantiate them. In other words, direct component graphs can't have cycles. Instead, we can use a's right to configure it's own components, to inroduce itself to b. In this case we don't need third object to connect these two, but we must complicate a's state with additional field like isInitialized which must be checked each time we use a.

astef
  • 338