I was browsing Wiki this morning and was struck by the idea to work out and construct a different / replacement for the LiskovSubstitutionPrinciple. After all, it is not that LSP is a bad heuristic -- even I encourage people to subclass when the subclass "is a kind of" the superclass . . . but just what does "is a kind of" mean, how do we have any idea when it is the right kind of, or wrong kind of, or not enough a kind of?
Attempt 0: The very purpose of subclassing is twofold: to assert that some code is the same, and to assert that some code will be different. The code that is the same should be "essential", in the sense that future mods are unlikely to invalidate them. The best initial heuristic for this is that the subclass "is a kind of" the superclass, but perhaps a special kind of, one that needs some particular tweaks. Thus this search pane "is a kind of" a general search pane, but has interdependencies that are special. This Cappucino "is a kind of" coffee-machine product, somehow different from the general, simplest and other products in the machine - - - hmm, now I'm already stuck, because when is it accidentally similar, and when is it enough the same to say "is a kind of" or "reliable for subclassing"? -- AlistairCockburn
to assert that some code is the same, and to assert that some code will be different. Why is more than this needed? A while ago we build a demo app for Java to show off the various capabilities of a certain hand-held device. In true XP style we built the first demo, then built the second one, then refactored the common code into superclasses. Then we built the third, and the fourth, and so on, and added such-and-so new global requirement, and changed the way various bits of the hardware were accessed, and so on and so forth. All the time we were pulling up common code, and introducing new intermediate classes in the hierarchy to allow new kinds of variation as we discovered the need for them. At the end of this we had taxonomies of GUI and application logic classes. These captured, for instance, that some demos needed to wait synchronously for some JNI calls to return, that others didn't. That some did network access and some didn't. And so on. That all demo screens had a "help" menu, some had menus specific to their function, some didn't. Etc. etc. And, reviewing the total requirement (once we'd got to the end of the project and had found out what that was), these taxonomies looked exactly like the ones I'd have expected to come up with by doing a very careful analysis job--if I were feeling particularly clear sighted and intelligent that day.
Isn't there a fundamental problem with trying to figure out inheritance hierarchies from the outside, as it were, before the fact and outwith any particular problem domain: that the way that all our current OO languages (programming or modelling) have built into them a very naive notion of category, and an even more naive notion of relationships between category. Notions so naive that they are more or less useless as the target of a mapping from the way people think about categories in the world (synthetically, multi-facetedly, fuzzily, prototypically, exemplar-ily, all mixed up together in a bucket.).
Inheritance in every OO language I've come across is a (very valuable) code management tool that turns out to sometimes be variously useful in representing certain very limited aspects of people's thinking about categories in the world. And I've further come to realise that this just isn't a problem. -- KeithBraithwaite
to assert that some code is the same, and to assert that some code will be different. - Wonderful! We need a word for that! [How about "inheritance" --KB] [Not quite. E.g. "We could introduce inheritance here, or factor out some code somewhere else, but we can't make just one class out of them - they have a lot of partial commonality" (or some other word) --FB]
Let me extend that thought by differentiating between interface and implementation. When the "same code" is in the interface and the "different code" in the implementation, you can express this via pure interface inheritance (e.g. Java interfaces). This is probably the case best described by the original LSP.
When the implementation has commonalities but the interface is different, there are a variety of ways to factor out the common code, including mix-in classes in languages with multiple inheritance (I would see a "pure mix-in class" as one that provides just implementation, but doesn't contribute to the interface of the inheriting class). Such inheritance does not express any LSP-style is-a relationship.
In cases where you have both, a fully or partially common interface and a partially common implementation, there's "normal" inheritance/subclassing, which provides both interface and implementation inheritance. LSP is a necessary precondition for this kind of inheritance, but not justification enough - if there's no significant commonality in implementation, pure interface inheritance is the better choice.
Another thought: There are two fundamentally different ways how to arrive at an inheritance relationship: the ontological/BDUF/holistic (;)) way ("X is, in its innermost true essence, a kind of Y"), and the empirical/refactoring/behaviouristic/craftite (;)) way ("factor out common interfaces, factor out common code, OAOO"), linked by the question whether one can emerge from the other. A "new LSP" could try to describe one, or the other, or both. --FalkBruegmann
Maybe. My complaint about naive categories is directed directly at the first of your two ways, the "ontological" way. I'd like to suggest that maybe we should abandon this route to inheritance hierarchies. As a professional software developer of commercial software, I don't believe that identifying the "innermost true essence" of things is my business (neither that doing so would deliver value to my customers), even if I knew a way to do that, which I don't.--KB