0

According to this answer (and many other articles from other sites), interfaces are the solution to solve circular references, but yet none of it explained "how and why"? (except assembly issues)

Here is what I don't understand:

Say, if we add a constructor to both Foo and Bar in the linked answer as follows:

public Foo()
{
    myBar = new Bar(this);
}

public Bar(Foo foo)
{
    myFoo = foo;
}

Then how is it "not referencing each other", since we can endlessly call myBar.myFoo.myBar.myFoo... and result in a "closed loop"?

I know I shouldn't "purposely" write constructors that will reference each other, but, in most cases, circular references are created, albeit bad design, because "A class wants to know something about B class and vice versa", so it wouldn't make any sense if someone creates 2 classes that "have nothing to do with each other". If that's the case, then there are no "circular reference issues" to begin with.

So, could somebody please be so kind explaining it to me?

Piggy Chu
  • 41
  • 6
  • 2
    Your example code doesn't seem to use interfaces at all. I would expect that `Bar` takes an `IFoo` interface. Also, whether you can call `myBar.myFoo.myBar.myFoo...` is a different thing: if you don't create visible fields or properties, you can't create a chain like this. – Lennart Jan 03 '23 at 10:43
  • Use the tool _nDepend_…all will be revealed. –  Jan 03 '23 at 10:47
  • @Lennart I'm sorry, I was merely extending it from the linked answer, I'll add it to the question. – Piggy Chu Jan 03 '23 at 10:49
  • You have to be a bit more specific. Two classes referencing each other is fine in many situations when those classes are closely related. Problems arise usually when there are circular references between classes in different assemblies or when trying to serialize object trees. Also, good design will use interfaces instead of direct class references for links between objects that are not closely related so that it is easier to exchange the concrete type to use. – NineBerry Jan 03 '23 at 10:55
  • 1
    What are you actually trying to solve? Things needing each other is a design pattern that works. Yes often other solutions are better but without a proper example no one can tell what the other option should be. There is no obvious best practice for something generic like Foo and Bar . – Ralf Jan 03 '23 at 10:55
  • @NineBerry So if I'm not doing all those things (putting them in different assemblies and serializing object trees), circular references are NOT a bad design? – Piggy Chu Jan 03 '23 at 10:59
  • from a semantics point of view circular dependencies are a completely valid thing. However you're mixing semantics with a technical concept here - which is the assembly. Interfaces won't change the semantics and thus won't change what your code does. They only change how you look at the data. – MakePeaceGreatAgain Jan 03 '23 at 11:01
  • @Ralf I just want to know why simply changing it to interface will solve the problem, since it is still essentially "referencing each other"? – Piggy Chu Jan 03 '23 at 11:01
  • @PiggyChu they might still are. It really depends on the context. Often, having bi-directional references indicates direct dependency that is too strong. But this can't be answered generically – Mafii Jan 03 '23 at 11:01
  • Oh, wow~! People should add these things to their answers/articles, having me scratching and banging my head for lacking the ability to come up with a "good design"! – Piggy Chu Jan 03 '23 at 11:06
  • 1
    An interface might solve the circular reference problems involving multiple assemblies. But thats it. Using an interface would still involve that things know each other and that isn't a problem per se (with an interface its atleast not a hard dependency). The problem is that its often not needed. So it has a smell to it and should be explicitly checked if really needed. But there are numerous example of such sort of references in the framework itself (DataTable <-> DataRow, Controls <-> ChildControls etc.). – Ralf Jan 03 '23 at 11:08
  • @Ralf I think this comment could be a valid answer, as it "answered my question", could you please be so kind elaborate on it a little bit more and make it an answer? Much appreciated! – Piggy Chu Jan 03 '23 at 11:11

1 Answers1

1

You're essentially mixing two things here. One beeing a conceptual view - that is what your program does. If you have a dependency of a Foo-instance to a Bar-instance and vice versa that's absolutely fine from a class-hierarchy-view. A father knows its children, and those know there father. You can introduce interfaces for father and children, however the dependency is still there - you only resolve it at runtime instead of at compile-time. So the interface won't change that semantics at all, it only moves resolving dependencies from compile-time to runtime.

The other point here is a more technical thing - where a class is located. As long as this is the same assembly, that forementioned circular dependency is no problem at all. If those are different assemblies, the compiler will omit an error. That's where an interface will make a difference, because you resolve the actual types at runtime instead of at compile-time.

MakePeaceGreatAgain
  • 35,491
  • 6
  • 60
  • 111