I've heard it said that it is "difficult" to "design for inheritance", but I've never found that to be the case. Can anyone (and by anyone, I mean Jon Skeet) explain why this is allegedly difficult, what the pitfalls/obstacles/issues are, and why mere mortal programmers should not attempt it and just make their classes sealed to protect the innocent?
ok, i kid about the latter - but i am curious to see if anyone (including Jon) really has difficulties "designing for inheritance". I have really never considered it an issue but perhaps I am overlooking something that I take for granted - or screwing something up without realizing it!
EDIT: thanks for all the excellent answers so far. I believe the consensus is that for typical application classes (WinForm subclasses, one-off utility classes, et al) there is no need to consider reuse of any kind, much less reuse via inheritance, while for library classes it is critical to consider reuse via inheritance in the design.
I don't really think about, say, a WinForm class to implement a GUI dialog, as a class that someone might reuse - I sort of think of it as a one-off object. But technically it is a class and someone might inherit from it - but it's not very likely.
A lot of the larger-scale development that I've done has been class libraries for base libraries and frameworks, so designing for reuse by inheritance was critical - I just never considered it to be "difficult", it just was. ;-)
But I also never considered it in contrast to the 'one-off' classes for common application tasks like WinForms et al.
More tips and pitfalls of designing for inheritance are welcome, of course; I'll try to throw in some, too.
To inherit from a class, use the extends keyword.
The union design pattern is a structural pattern that depicts the inheritance relationship between a superclass and its subclasses. The superclass is an abstract representation of the union of all the subclasses. Due to this polymorphism, the subclasses can thus be used wherever the superclass is required.
I think the main point is in the second-to-last paragraph in Jon's response: Most people think of "designing for inheritance" in terms of application design, i.e. they just want it to work, and if it doesn't, it can be fixed.
But it's a whole different beast to design an inheritable class as an API for other people to work with - because then the proteced fields and a whole lot of implementation details implicitly become part of the API contract which people using the API have to understand, and which cannot be changed without breaking the code using the API. If you make a mistake in the design or the implementation, chances are you can't fix it without breaking code that depends on it.
Rather than rehashing it too much, I'll simply answer that you should have a look at the problems I had when subclassing java.util.Properties. I tend to avoid many of the problems of designing for inheritance by doing so as rarely as possible. Here are a few ideas of the problems though:
It's a pain (or potentially impossible) to implement equality properly, unless you limit it to "both objects must have exactly the same type". The difficulty comes with the symmetric requirement that a.Equals(b)
implies b.Equals(a)
. If a
and b
are "a 10x10 square" and "a red 10x10 square" respectively, then a
might well think that b
is equal to it - and that may be all you actually want to test for, in many cases. But b
knows that it has a colour and a
doesn't...
Any time you call a virtual method in your implementation, you've got to document that relationship, and never, ever change it. The person deriving from the class needs to read that documentation, too - otherwise they might implement the call the other way round, quickly leading to a stack overflow of X calling Y which calls X which calls Y. This is my main problem - in many cases you have to document your implementation which leads to a lack of flexibility. It's mitigated significantly if you never call one virtual method from another, but you still have to document any other calls to virtual methods, even from non-virtual ones, and never change the implementation in that sense.
Thread safety is hard to achieve even without some unknown code being part of your execution-time class. You not only need to document the threading model of your class, but you may also have to expose locks (etc) to derived classes so they can be thread-safe in the same way.
Consider what sort of specialization is valid while keeping within the contract of the original base class. In what ways can methods be overridden such that a caller shouldn't need to know about the specialization, just that it works? java.util.Properties is a great example of bad specialization here - callers can't just treat it as a normal Hashtable, because it should only have strings for keys and values.
If a type is meant to be immutable, it shouldn't allow inheritance - because a subclass can easily be mutable. Oddities could then abound.
Should you implement some sort of cloning ability? If you don't, it may make it harder for subclasses to clone properly. Sometimes a memberwise clone is good enough, but other times it may not make sense.
If you don't provide any virtual members, you may well be reasonably safe - but at that point any subclasses are providing extra, different functionality rather than specializing the existing functionality. That's not necessarily a bad thing, but it doesn't feel like the original purpose of inheritance.
Many of these are much less of a problem for application builders than class library designers: if you know that you and your colleagues are going to be the only people ever to derive from your type, then you can get away with a lot less up-front design - you can fix the subclasses if you need to at a later date.
These are just off-the-cuff points, by the way. I may be able to come up with more if I think for long enough. Josh Bloch puts it all very well in Effective Java 2, btw.
I don't think that I ever design a class for inheritance. I just write as little code as possible, and then when it's about time to copy and paste to create another class, I bump that one method into a super class (where it makes more sense than composition). So I let the Do Not Repeat Yourself (DRY) principle dictate my class design.
As for the consumers, it's up to them to act sanely. I try not to dictate how anyone can use my classes, although I do try to document how I intended them to be used.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With