Recipe 3.4. Implementing Polymorphism with Abstract Base ClassesProblemYou need to build several classes that share many common traits. These classes may share common properties, methods, events, delegates, and even indexers; however, the implementation of these may be different for each class. These classes should not only share common code but also be polymorphic in nature. That is to say, code that uses an object of the base class should be able to use an object of any of these derived classes in the same manner. SolutionUse an abstract base class to create polymorphic code. To demonstrate the creation and use of an abstract base class, here is an example that makes use of three classes, each defining a media type: magnetic, optical, and punch card. An abstract base class, Media, is created to define what each derived class will contain, as shown in Example 3-1. Example 3-1. Implementing an abstract base class (Media)
Next, the three specialized media type classes Magnetic, Optical, and PunchCard, which inherit from Media, are defined to override each of the abstract members, as shown in Example 3-2. Example 3-2. Implementing derived classes (Magnetic, Optical, and PunchCard)
In Example 3-3, the methods TestMediaABC and UseMedia show how any of the three media types can be used polymorphically from within the UseMedia method. Example 3-3. Using derived classes polymorphically
The output of these methods is shown here: Magnetic Init Magnetic Write Magnetic Read 0 Magnetic Close Magnetic Optical Init Optical Write Optical Read 0 Optical Close Optical PunchCard Init PunchCard Write PunchCard Read 0 PunchCard Close PunchCard DiscussionPolymorphism through an abstract base class is a powerful tool. With this tool, you are able to create a method (UseMedia in this solution) that accepts a parameter with a specific type that is known only at runtime. Since the use of this parameter is similar for all objects that can be passed in to this method, you do not have to worry about the specific class that is passed in; you need to know only how the abstract base class is defined. It is through this abstract base class definition that you know how to use the specific type. There are several things to keep in mind when using an abstract base class:
It is possible to use interfaces to implement polymorphism; this is discussed at length in Recipe 3.16. There are two advantages to using an abstract base class over an interface:
You should also consider using an abstract base class over an interface when a lot of disparate members need to be overridden in the derived classes. For example, if you are implementing a set of members that control searching or sorting of items, you should initially consider interfaces, since this is a focused set of members that may be implemented over a wide range of unrelated classes. If you are implementing a set of members that determines the base functionality for a complete type, such as the Media type, you will probably want to use an abstract base class. See Recipe 3.16 for the advantages of using interface polymorphism over abstract base classes. There may be some advantages in fully implementing a base class, so that it becomes a concrete class rather than an abstract one. This is especially the case when you are adding functionality to an existing base class. Your implementations do not need to be elegant; they can do nothing at all, or they can throw a NotImplementedException. The most important advantage implementing a base class as a concrete class is that derived classes do not have to override all, or for that matter any, base class members. (Of course, it makes no sense for derived classes not to implement at least one member.) With abstract base classes, you may have a number of derived classes that provide do-nothing implementations. By moving a do-nothing implementation into the base class, you save writers of derived classes from having to implement them. Notice that the abstract Media class in this recipe could be written as a concrete class (i.e., remove the abstract keyword and implementations of its abstract methods). This will allow derived classes to ignore any members they aren't interested in and focus on only the ones they need to override. This, of course, is also the disadvantage of concrete base classes; derived classes can ignore members that they should pay attention to. The compiler will not allow a derived class to ignore any members declared as abstract. Example 3-4 shows the abstract Media class from earlier in this recipe (see Example 3-1) rewritten as a concrete class. All that is necessary is to remove the abstract keyword and add implementations to all abstract methods. This allows you to create objects from the Media class. If you do not wish for objects to be created from your base class (Media), you can declare it as abstract, even though all its members are fully implemented. In this case, Init and Close are left as do-nothing methods. WriteTo and ReadFrom tHRow a NotImplementedException. This, in effect, requires derived classes to implement them, but it moves this requirement from compile time to runtime. Example 3-4. Implementing a concrete base class (Media)
It is not necessary to change any of the derived classes or the driver methods (TestMediaABC and UseMedia). See AlsoSee Recipe 3.16; see section 10.1.1.1, "Abstract Classes," in the C# Language Specification. |