> I'd argue some amount of "co-recursion" is to be expected: after all the point of the child class is to reuse logic of the parent
That's the point: You can reuse code without paying that price of inheritance. You DON'T have to expect co-recursion or shared state just for "code-reuse".
And, I think, is the key point: Behavior inheritance is NOT a good technique for code-reuse... Type-inheritance, however, IS good for abstraction, for defining boundaries, to enable polymorphism.
> I'd say this is a fact of life for all pieces of code which are reused more than once
But you want to minimize that complexity. If you call a pure function, you know it only depends on its arguments... done. If you can a method on a mutable object, you have to read its implementation line-by-line, you have to navigate a web of possibly polymorphic calls which may even modify shared state.
> This is another reason why low coupling high cohesion is so important
exactly. Now, I would phrase it the other way around though: "... low coupling high cohesion is so important..." that's the reason why using inheritance of implementation for code-reuse is often a bad idea.
I actually can't imagine for the life of me why I'm defending OOP implementation hierarchies here- I guess I got so used to them at work, I've changed my strategy from opposing them to "it's okay as long as you use them sparingly". I have found that argument to do a lot better with my colleagues...
That's not true. If Outer has a member Inner, Outer always has to invoke `my_inner.foo()` to use Inner::foo, and `foo()` always refers to Outer::foo (and some languages will force you to write `self.foo()`, which is even better).
If Outer extends Inner, though, you can't tell whether `foo()` refers to Inner::foo or Outer::foo without checking to see whether Outer overrides foo or not. And the number of places you have to check scales linearly with the depth of the inheritance hierarchy.
If object A calls a method of object B (composition), then B cannot call back on B, and neither A nor B can override any behavior of the other (And this is the original core tenet of OO: being all about "message-passing").
Of course they can accept and pass other objects/functions are arguments, but that would be explicit and specific, without having to expose the whole state/impl to each other.
That's the point: You can reuse code without paying that price of inheritance. You DON'T have to expect co-recursion or shared state just for "code-reuse".
And, I think, is the key point: Behavior inheritance is NOT a good technique for code-reuse... Type-inheritance, however, IS good for abstraction, for defining boundaries, to enable polymorphism.
> I'd say this is a fact of life for all pieces of code which are reused more than once
But you want to minimize that complexity. If you call a pure function, you know it only depends on its arguments... done. If you can a method on a mutable object, you have to read its implementation line-by-line, you have to navigate a web of possibly polymorphic calls which may even modify shared state.
> This is another reason why low coupling high cohesion is so important
exactly. Now, I would phrase it the other way around though: "... low coupling high cohesion is so important..." that's the reason why using inheritance of implementation for code-reuse is often a bad idea.