2005-04-11

Divided Classes: Having your subclassing and not eating fragility too

It's a maxim of OOP that "inheritance breaks encapsulation". The difficulty is that in order to subclass a class and override some of its methods, you have to make sure that the method you are overriding is actually being invoked by other methods that you are not overriding. and that they aren't just bypassing you.

The usual solution to this problem amounts to "Forget inheritance; use delegation of one kind or another instead" or else "Document the connections for subclassers and let them hope they can trust you to get it right."

There is, however, a general method for preventing this problem, which consists in dividing your classes into what I will call, unoriginally, divisions. A division of a class is made up of a part of the class's state variables and all the methods that refer to the instance variables in that part. (State variables are instance variables, except for ones that immutably refers to an immutable object; a final String variable in Java, for example, is not part of the state.)

In particular, to divide an existing class into divisions, start with any state variable, then include all the methods that refer to it, then include all the state variables referred to by those methods, and so on until there's nothing more to do. That's one division. Then start with any remaining state variable, do the same thing, and so on until there are no more state variables. Any remaining methods are convenience methods, and are put into divisions by themselves. We can ignore private methods in this process, since they aren't visible to subclasses.

Now the rule is, When subclassing, you must override all the methods in a division or none of them. With all the methods in a division overridden, all the state shared by those methods is irrelevant to the subclass, and and other methods in the superclass don't refer to that state in any way. So encapsulation isn't broken by subclassing in this style.

Furthermore, you must not merge divisions in the subclass.. That is, there must be no shared state between an overriding method in one division and an overriding method in another division. That keeps you safe from having one overridden method call another that corrupts its subclass state. You can add state variables to each overriding division, though, because you control everything.

I can't claim credit for inventing this; it was written up in a paper (seemingly unavailable on line) called "Modular Reasoning in the Presence of Subtyping". I have reinvented the terminology as well. If anyone remembers the source, please tell me and I will credit it. Thanks.