I try to avoid inner classes in general unless they are simple lambdas, listeners with one or two lines of code, etc.
Once an inner class has a life of its own and performs more complex functions, I refactor it into its own top-level class. I will use either a standard interface or create my own for the class that used to enclose it (A
). I then construct the B
class and pass in a reference to the interface that A
now implements and is used by B
.
While A
and B
are tightly coupled, they probably do not need to be. Severing that coupling and using interfaces and references rather than an implicit outer-class this
pointer/reference now means B
is one step closer to being reusable. There is now an abstraction between them.
Often the reason for introducing another class is that the enclosing class needs to do additional work that it cannot do itself. That additional work is likely (in my experience) to violate the Single Responsibility Principle. Moving it into an inner class is one step toward fixing that, but is (in my opinion) a suboptimal fix. Refactoring as I mentioned above will fix the tight coupling and "multiple responsibility" concerns.
One final reason to split into multiple top-level classes is it makes the source files shorter. This has nothing to do with OOP principles and is purely aesthetic.