I've just finished watching another interesting lesson from Brad Abrams of Microsoft, this time focusing on designing inheritance hierarchies. I've summarized what I learnt below. Of course you should watch it yourself if you have the time!
1. Dangers of Over-Designing. Version 1 should ALWAYS be as simple as possible, meeting only the minimum of the immediate requirements. The use cases and actual requirements aren't know until Version 2 and it's very easy to get carried away by imaginary requirements and end up with a design that's so complex that it ends up being rewritten for Version 2 anyway.
2. Give preference to broad, shallow hierarchies. Try not to go deeper than 3 levels, as deep hierarchies become difficult to maintain and extend.
JW: Personally I find it amusing how the “book smarts“ who learn everything about object orientation from books go straight in and over-design their classes. Sure it's comforting to think you've coded for every possible eventuality, but reality is always quite different. There's nothing quite as discomforting as being bogged down with maintenance while the requirements are piling up. Brad's advice here is priceless.
3. Consider making base classes abstract, to give a clear message that they are not complete and are intended to be extended to provide real implementation.
4. Virtual members are both powerful and can be dangerous. Powerful because of their extensibility, but dangerous because the your code can become more fragile as the true implementation of the methods isn't known. As such, virtual members should be used sparingly.
5. When overriding, don't change the semantics of the member. You should be consistent with the contract defined by the base class.
6. Have a concrete scenario for every virtual member you define: Try to provide an example of when the member would need to be overridden.
7. Ensure that base members calling the virtual will still be able to function reliably with different implementations of the virtual. This may require some defensive coding when calling virtuals in your base classes.
8. Consider making the virtual member an abstract member when the class cannot provide any meaningful implementation of a virtual member, .
9. When possible, use base classes over interfaces. Base classes let you provide a default implementation, making it easier to sub-class and customize. Interfaces require a total re-implementation making it far more difficult to use.
10. Interfaces also have versioning problems that base classes avoid. An interface is a contract and should not be be modified once released. Adding a new method to the interface will break existing classes that implement that interface (as the new method implementation will be missing). Base classes can provide default implementation, and so existing sub-classes will continue to function correctly across versions.
11. An alternative to providing default implementation is by using aggregation, where a default implementation is either contained or delegated to, and exposed through methods returning contained instances or through interface methods that delegate. This is quite common in the COM world, and provides an interesting solution where multiple inheritance is required. [a similar pattern is the decorator pattern].
12. Interfaces are useful for letting you access discreet aspects of an object's functionality, when you want to hide (or not have to be bound to) the true identity of the object.
13. If you are using interfaces, keep the interfaces very small (1 or 2 members is best). IComparable is a good example.
14. You don't always want interface methods to clutter your implementation type. Consider using private interface method implementations (known as explicit method implementations). For example 'int IComparable.ConvertTo(object obj)' will hide the method at the class level, but make it visible when cast to the interface.
15. Private interface method implementations helps with interface versioning, because an object can implement two different interfaces with the same method signature.
Thanks Brad for the useful tips.