or years, Visual Basic programmers pleaded with Microsoft to give them the object-oriented programming (OOP) capabilities that were enjoyed by C++ and Java developers. While a few OOP-related features were added to the past couple of releases, we had to wait for Visual Basic.NET to deliver true OOP to Visual Basic. One of the things you can do with OOP is create abstract classes. This programming tool is often overlooked—which is a shame because abstract classes offer a lot of possibilities to the developer.
What you need:
Some familiarity with the fundamentals of object-oriented programming and with Visual Basic.NET.
It's easy to explain what abstract classes are. They're simply classes that cannot be instantiated. You cannot create an object from such a class for use in your program.
It's trickier to explain why they're interesting. You might think that a class that cannot be instantiated is not much use. Not true. In certain situations, abstract classes can be very useful. You can use an abstract class as a base class, creating new classes that inherit from it. Creating an abstract class with a certain minimum required level of functionality gives you a defined starting point from which you can derive non-abstract classes.
Here's a hypothetical programming situation that would benefit from an abstract class. Suppose your graphics application provides a variety of drawing classes: circle, rectangle, Bézier curve, and so on. All these drawing classes have certain members in common—such as properties for position, size, and color, as well as a method to erase the drawn object. Other class members, such as the method for actually rendering the shape, differ for each individual class (type of shape). This makes an ideal situation for an abstract class.
Figure 1. Possible inheritance structure for an abstract class.
In the abstract class you define all the common class members that are needed by all drawing classes. Because it doesn't correspond to any shape, or have a rendering method, there is no reason for a program ever to instantiate it. But by using it as the base class for all your shape-specific drawing classes, you ensure that all these non-abstract classes have the required basic functionality. Figure 1 shows a possible inheritance structure for this scenario.
Creating an Abstract Class
Defining an abstract class is as simple as including the MustInherit keyword in the class definition:
Public MustInherit Class MyAbstractClass
Aside from this keyword, an abstract class definition is no different from a non-abstract class. It can contain properties and methods, inherit from another class, implement an interface, and so on. However, you must be particularly aware of how class members (properties and methods) that are defined in the abstract class relate to members that can be defined in derived classes. There are no hard and fast rules here, but the decisions you do make will be related to the functionality of your class and its planned derived classes.
You have three choices:
- Provide functionality in the abstract class as a property or method that can, but does not have to, be overridden in a derived class. This is done by using the Overridable keyword in the member definition. The following code defines the Move() method. A derived class can either use the Move() functionality provided by the abstract base class or provide its own functionality by overriding the method:
Public Overridable Sub Move(NewX As Integer, NewY As Integer)
In order to override a member of an abstract base class, a derived class must use the Overrides keyword in its own implementation of the member:
Public Overrides Move(NewX As Integer, NewY As Integer)
- Provide functionality in the abstract class that cannot be overridden in derived classes. That is, derived classes must use the member as defined in the abstract class. This is the default—the result if you define a member in the abstract class with no special keywords. The following code defines the XPos property in the abstract class. Derived classes must use this implementation of the property because it cannot be overridden:
Public Property XPos() As Integer
- Use the MustOverride keyword to define an abstract class member that must be overridden in derived classes. The following example specifies that derived classes must implement a method named GetObjectAt():
Public MustOverride Function GetObjectAt(X As Integer, _
Y As Integer) As Object
Note that members declared as MustOverride do not have an End Sub or End Function statement associated with them. The beauty of using MustOverride is that it specifies not only the name but also the signature (arguments and return type) of the member. This previous example, therefore, requires that any derived class define a method named GetObjectAt() that takes two type Integer arguments and returns a type Object. How the method is implemented in the derived class is totally up to the programmer as long as these requirements are met. Methods declared with the MustOverride keyword are sometimes called abstract members.
Abstract Classes vs. Interfaces
If you are familiar with interfaces, you may be thinking that an abstract class does much the same thing. This is only partly true. Defining an abstract class that contains only abstract members has essentially the same effect as defining an interface, because you specify that derived classes must implement certain members with specific signatures. Beyond this, however, abstract classes provide additional capabilities—specifically, the definition of base functionality in the form of non-abstract members, something that an interface cannot do.
When you think that either an abstract class or an interface would work, keep these considerations in mind:
- A derived class can implement multiple interfaces, but can inherit from only one class (abstract or not).
- A class that subclasses an abstract class can still implement one or more interfaces.
Depending on the needs of your project, you may rely on a single abstract class, one or more interfaces, or a combination of an abstract class with an interface. Visual Basic and .NET provide a great deal of flexibility in this regard, and you can often achieve the same result in more than one way. The bottom line is that there are some things that just cannot be done without using an abstract class. Abstract classes may be thought of as a rather specialized programming tool. When the situation calls for it, they reduce the developer's work and lead to a simpler, more robust application.
An Abstract Class in Action
Imagine developing a new employee records system for a large multinational corporation. Your job is to supervise the implementation of classes to represent employees such that certain core requirements of the head office are met, while still providing flexibility for differing requirements from company branches elsewhere.
The core requirements are as follows:
- Name and DateOfHire properties will be implemented in the abstract class and cannot be overridable.
- A RetirementID property will be implemented in the abstract class to handle Social Security numbers because most employees live in the United States. Branches in other countries will use different ways to identify an employee's retirement identification, so this property will be overridable in derived classes to permit individual branches to implement it differently as required.
- A method called Compensation will take no arguments and will return a type Object containing details of the employee's compensation. Because compensation—salary, commissions, bonuses, and so on—are handled differently at the various branches, complete flexibility in implementing this method is necessary; it will be made an abstract method.
The code for the resulting abstract class, called EmployeeBase, is shown in Listing 1. You'll see that I've omitted the actual implementation of the base members because those details are not relevant here.
At the company office in Paris, France, a programmer is using the EmployeeBase class as the basis for the EmployeeFrance class (see Listing 2) to use with the local employee records software. This derived class will necessarily inherit the Name and DateOfHire members. Furthermore, the RetirementID member in the EmployeeBase class is suitable for use in France, so the new class will not override it. All that the programmer has to do is implement a method to override the abstract Compensation member.
In London, England, however, the base class's RetirementID property is not suitable, so the derived class (called EmployeeEngland, shown in Listing 3) will override that member as well as the Compensation member. The implementation of Compensation will likely be different from the one used in France.