Recipe3.4.Implementing Polymorphism with Abstract Base Classes


Recipe 3.4. Implementing Polymorphism with Abstract Base Classes

Problem

You 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.

Solution

Use 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)

 public abstract class Media {     public abstract void Init( );     public abstract void WriteTo(string data);     public abstract string ReadFrom( );     public abstract void Close( );     private IntPtr mediaHandle = IntPtr.Zero;     public IntPtr Handle     {        get {return(mediaHandle);}      }  } 

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)

 public class Magnetic : Media {     public override void Init( )      {        Console.WriteLine("Magnetic Init");     }     public override void WriteTo(string data)     {        Console.WriteLine("Magnetic Write");     }     public override string ReadFrom( )     {        Console.WriteLine("Magnetic Read");        string data = "";        return (data);     }     public override void Close( )      {        Console.WriteLine("Magnetic Close");     } } public class Optical : Media {     public override void Init( )      {        Console.WriteLine("Optical Init");     }     public override void WriteTo(string data)     {        Console.WriteLine("Optical Write");     }     public override string ReadFrom( )     {        Console.WriteLine("Optical Read");        string data = "";        return (data);     }     public override void Close( )     {        Console.WriteLine("Optical Close");      } } public class PunchCard : Media {     public override void Init( )      {        Console.WriteLine("PunchCard Init");     }     public override void WriteTo(string data)     {        Console.WriteLine("PunchCard WriteTo");     }     public override string ReadFrom( )     {        Console.WriteLine("PunchCard ReadFrom");        string data = "";        return (data);     }     public override void Close( )      {        Console.WriteLine("PunchCard Close");      } } 

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

 public static void TestMediaABC( ) {     Media x = new Magnetic( );      UseMedia(x);     Console.WriteLine( );     x = new Optical( );     UseMedia(x);     Console.WriteLine( );     x = new PunchCard( );     UseMedia(x);  } private static void UseMedia(Media media) {     media.Init( );      media.WriteTo("text");      media.ReadFrom( );      Console.WriteLine(media.Handle);      media.Close( );     Console.WriteLine(media.ToString( ));  } 

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 

Discussion

Polymorphism 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:

  • Neither this class nor its abstract members can be declared as sealed; this would defeat polymorphism.

  • The abstract class cannot be instantiated using the new operator, but a variable can be declared as belonging to an abstract base class type.

  • All abstract members must be overridden in a derived class unless the derived class is also abstract.

  • It is implied that an abstract method is also defined as virtual.

  • Only methods, properties, indexers, and events may be declared as abstract.

  • Abstract methods, properties, and indexers may not be declared as static or virtual.

  • If an abstract base class implements an interface, it must provide either an implementation for the interface members or an abstract definition of the interface members. A combination of the two may be applied as well.

  • An abstract base class can contain abstract and nonabstract members. It is not required to contain any abstract members, but this omission may confuse those who read this code.

  • An abstract class may implement any number of interfaces and may also inherit from a single class. As a note, abstract members may override virtual members in the nonabstract base class.

  • A derived class can override abstract properties and must include at least one accessor method (i.e., get or set). A property in a derived class that overrides an abstract property implementing only a get or a set accessor must override that specific get or set accessor. If the abstract property implements both a get and a set accessor, the overriding class property may override one or both accessors.

  • A structure cannot implement polymorphism through an abstract base class/ structure. Instead, a structure should consider implementing polymorphism through interfaces (see Recipe 3.16).

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:

  • Abstract base classes allow greater flexibility in versioning. An abstract base class can add a nonabstract member without breaking existing derived classes; an interface cannot. You can also add new abstract members to the class without breaking derived classes, as long as you provide default implementations for them.

  • An abstract base class can contain both abstract members and nonabstract members. An interface may contain only definitions of members with no implementation.

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)

 public class Media {     public void Init( )     {     }     public void WriteTo(string data)     {        throw new NotImplementedException();     }     public string ReadFrom( )     {        throw new NotImplementedException();     }     public void Close( )     {     }     private IntPtr mediaHandle = IntPtr.Zero;     public IntPtr Handle      {        get {return(mediaHandle);}     }  } 

It is not necessary to change any of the derived classes or the driver methods (TestMediaABC and UseMedia).

See Also

See Recipe 3.16; see section 10.1.1.1, "Abstract Classes," in the C# Language Specification.



C# Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2004
Pages: 424

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net