DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 3.4 Polymorphism via Concrete or 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 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 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:

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, which inherit from Media, are defined to override each of the abstract members:

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");
    }
}

The following methods, TestMediaABC and UseMedia, show how any of the three media types can be used polymorphically from within the UseMedia method:

public void TestMediaABC( )
{
    Media x = new Magnetic( );
    UseMedia(x);

    Console.WriteLine( );

    x = new Optical( );
    UseMedia(x);
}

private 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

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 whose specific type is known only at runtime. Since the use of this parameter is similar for all objects that can be passed in to this method, we do not have to worry about the specific class that is passed in; we need to know only how the abstract base class is defined. It is through this abstract base class definition that we 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 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 base 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 base 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.17).

It is possible to use interfaces to implement polymorphism; this is discussed at length in Recipe 3.17. There are several advantages to using an abstract base class over an interface:

  • Abstract base classes allow more flexibility in versioning. An abstract base class can add a nonabstract member without breaking existing derived classes; an interface cannot. However, adding a new abstract member will break the existing derived classes.

  • An abstract base class can contain abstract members as well as 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 were implementing a set of members that determines the base functionality for a complete type, such as the Media type, you would want to use an abstract base class. See Recipe 3.17 for the advantages of using interface polymorphism over abstract base classes.

Notice that the abstract Media class in this recipe could be written as a concrete class (i.e., remove the abstract keyword and add implementations to all abstract methods). This would allow you to create objects from the Media class. If you do not wish for objects to be created from your base class (Media), you should make it abstract.

See Also

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

    [ Team LiB ] Previous Section Next Section