Let's consider a really simple object graph:
A -> B
C
A holds a reference to B, C has no reference to other objects. A is responsible for Bs state. By the principles of encapsulation, B is part of As state.What if it turns out later, that C has business with B? It cannot pass a message to A, or B. So, we do this?
A -> B <- C
Wait, no, we can't do that, because then B would be part of Cs state, and we violate the encapsulation. So, we have 2 options: 1. C -> A -> B
2. C <- X -> A -> B
Either we make C the god-object for A, or we introduce an abstract object X which holds references to A and C (but not to B, because, encapsulation). Both these implementations are problematic: in 1) A now becomes part of Cs state despite C having no business with A, and in 2) we introduce another entity in the codebase that serves no purpose other than as a mediator. And of course, A needs to be changed to accomodate passing the message through to B.And now a new requirement comes along, and suddenly B needs to be able to pass a message back to C without a prior call from C. B has no reference to A, X or C (because then these would become part of its state). So now we need a mechanism for B to mutate its own state being observed by A, which then mutates its own state to relay the message up to X, which then passes a message to C.
And we haven't even started to talk about error handling yet.
Very quickly, such code becomes incredibly complex, so what often happens in the wild, is: People simply do this:
A -> B <-> C
And at that point, there is no more encapsulation to speak of. B, and by extension B is part of A's state, and B is part of Cs state.