The contrived classes in some way muddy the issue. It somewhat depends on what you're trying to do, or specifically how the object graph that you're working with is defined. The jump from breakfast
to yolk
is unnatural...you'd be unlikely to jump directly to the yolk child of the breakfast aggregate.
In this case the contract would likely be that the breakfast would deliver the egg
object, so that is what you would return, and then get the yolk from there if needed (but then that only involves the egg
object...breakfast
stops caring.
If for some reason you did want to consistently grab the yolk, then you should use delegation. You never want your client code to care about more than it has to or should (the Law of Demeter above), and you never want to have anything but the defined and managed API leak to the client code.
A more reasonable example would be supposing that you have eggs in different forms with breakfast, and so it may come from a plate, or a little soft boiled egg stand, or from a glass, "Rocky" style. In that case the client code still just wants the egg, so you'd want to use delegation and fetch the egg out of the appropriate plate, stand, or glass child.
It comes down to defining what it is you are exposing to the client code, and make sure that any baggage or implementation details are handled and abstracted by the receiving object. You always want to work in terms of what you are delivering and not where it is coming from.