Recently, there was an article on avoiding inheritance on the Visual Studio start page news channel. In general, I like the content of the article. I don't think there is any misinformation, but I do think there is some misrepresentation. I believe this article is overly critical of inheritance. I hope the author isn't simply being fashionable by deriding inheritance in favor of generics and lambdas.
One realization I have come to when talking with many other developers is that there is a good number of developers that don't realize that inheritance introduces dependencies directly into your classes.
This is a problem of education, not abstraction. This doesn't mean inheritance is flawed; it means the developers' knowledge is incomplete.
Your derived class is now dependant [sic] upon the interface and behavior of your base class. Worded another way, your derived class is now strongly coupled to your base class.
Correct: that is by design. That is a known and welcome aspect of inheritance. If Foo inherits Bar, a Foo is a Bar and I expect it to behave as such.
For instance, if I need to add another abstract method I didn't originally think of, every single class that derives from that base class now must change in order to implement that behavior.
If you're adding an abstract method, it means you want derived classes to implement it. Otherwise, either add a virtual method or derive a new class that contains the new abstract method.
Or, if I change the behavior of one of the methods in my base class, I can find myself in a situation where one of my derived classes will break because they were relying on that base class behavior to act a given way….
This problem isn't restricted to class hierarchies; this will happen with any code. Any time you change code anywhere, you need to re-test your application. If you're writing a class library for consumption by others, you're only alternative to potentially breaking a client is to add a new method with the new behavior or create an entirely new class (e.g. the ConfigurationManager class instead of the ConfigurationSettings class). The .NET framework even has an attribute (the ObsoleteAttribute class) specifically for dealing with legacy. There are plenty of instances of this in the System.Runtime.InteropServices namespace.
Everything else about the article is pretty good. His use of the Command pattern is a fine example of when to use generics instead of inheritance. I have a couple of additions to his discussion.
- And because of leveraging a common base class, we can use polymorphism to reuse this code even more (like having a list of commands that we can execute in a script or elsewhere).
I think this is still useful but he didn't come back to it in his re-implementation using generics. Here, he can still benefit from an interface. Having his GenericCommand<TReceiver> implement, say, ICommand (having the same definition as his Command abstract class) enables generic containers and iteration.
- First of all, for every home device that we wish to turn on and off, we would be introducing two new classes into our code.
In his example where he addresses this with generics, .NET will introduce new classes (e.g. GenericCommand`1[Garage]), but they're created at run-time. You won't find them if you use, say, Reflector to look at the assembly. This doesn't invalidate the way he addresses this concern since those generated classes aren't introduced into our code.
- If we change our Execute() method in our base class from abstract to virtual in order to introduce common behavior, how do you guarantee that the base method is called when it should be? In a derived class, should you call the base method before your own execution code, or after? What if I want common code in my base class that will be executed both before and after the behavior in my derived classes?
The answer to all of these is "use the Template Method pattern." In fact, using lambdas to defer the actual implementation of the command behavior to a separate entity as he does is in essence a use of the Strategy pattern, a relative of the Template Method pattern. (BTW, Wikipedia's Python example of the Strategy pattern can be re-implemented in C# without loss of generality by using the same techniques found in this article.)