At this point, you can sit back and admire your code. Soon, however, your satisfied smile turns to a frown because you realize you've written some RDC (really dumb code) into the program. Given the way you've designed the Building class, why would anyone ever want to directly create a Building object? The real value of the base Building class is that it is a generic collection point for the common data items that are part of a specific type of building. In other words, the base class probably doesn't have enough specific information to be very useful as a standalone object.
In situations like this, you might want to prevent users from creating objects from the base class. You want to use the base class only as a point from which to create derived classes. A class for which you do not expected to have objects created is called a virtual class. (You might hear programmers refer to virtual classes as abstract classes, too. They are the same thing.) Virtual classes behave like placeholders from which all derived classes are created.
The MustInherit Keyword
If you do not want anyone to create an object of class Building , you can use the MustInherit keyword. This tells Visual Basic .NET that no object can be created directly from that class. To implement this feature, you take the top line from Listing 17.1:
Public Class Building
and change it to this:
Public MustInherit Class Building
To test this change, you simply add a line that attempts to define an object of the base class. To do so, look at the top several lines of Listing 17.3, and you should find this:
Public Class frmBuilding Inherits System.Windows.Forms.Form Dim MyApartment As New Apartment() Dim MyBuilding As New Commercial() Dim MyHome As New Home()
Now add the following new line after the preceding code:
Dim MyBldg As New Building()
The new line is an attempt to create a Building object from a virtual class. When you type this line into the program and press the Enter key, Visual Basic .NET gets a tad miffed and flags the statement as containing an error. If you move the cursor over the word Building in the statement line, you get this message:
'New' cannot be used on a class that is declared 'MustInherit'
Visual Basic .NET is smart enough to recognize the error at design time and inform you that you cannot create a Building object. (The same error message would be repeated if you tried to compile the program.) The MustInherit keyword, therefore, makes the base Building class a virtual class from which no objects can be created.
Keywords That Affect Inheritance
Thus far, this chapter has discussed the major structure and keywords that you need to know to create a base class. It has also shown how to extend that base class by deriving new classes from the base class. There are, however, several variations that you can use to alter the basic behavior that exists between base and derived classes. The following sections describe those keywords.
The Overridable and Overrides Keywords
Oftentimes, a base class contains a property or method that suits the needs of most derived classes but not all. For example, you might have a base class method named ReadPropertyTaxFile() that reads property tax data from a data file. However, suppose an investor purchased from the city a piece of property that has special tax incentives if the investor rehabs the building. For such a situation, you might want to create a new derived class named RehabProperty . The base class ReadPropertyTaxFile() method, however, would not read the proper file for a RehabProperty building. In fact, the investor might actually get a credit on his taxes instead of having to pay taxes.
However, because you want to preserve the way the class interface works, you might still want to use the ReadPropertyTaxFile() method but change the way it works in the RehabProperty class. To do this, you would write the following in the base class:
Overridable Sub ReadPropertyTaxFile() ' Insert the code to read the normal tax file. End Sub
The Overridable keyword tells Visual Basic .NET that derived classes have the capability to replace the RehabProperty() procedure in the base class with a different version if they want. If a derived class does not replace the Overridable procedure in the base class, the base class procedure is used.
In the RehabProperty class, you would write this:
Overrides Sub ReadPropertyTaxFile() ' Insert the code to read the incentive file. End Sub
The Overrides keyword tells Visual Basic .NET that this procedure is overriding a procedure that is defined in the base class. Therefore, any RehabProperty object that you define in your program uses the ReadPropertyTaxFile() method from the derived class rather than the method with the same name in the base class.
Clearly, the intent of these two keywords is to allow you to accommodate the special cases that arise in the derived classes. Hmmm. Instead of writing all this override stuff, why can't you just write a special method with a different name for the derived class that needs it?
Well, you could do that. However, if you did, you would force yourself to cope with a new method. Worse yet, the method might be used in only one class, and you might totally overlook it. If you can accommodate the necessary special cases for the derived class by using the same method name, you maintain the same interface for each derived class. This simplifies the things for you, the programmer.
Overriding the base class implementation also means that you've hidden away the implementation code for the special cases, which means you can use the same black box for all derived classes, even though what's going on inside that black box might be considerably different for each derived class.
The MustOverride Keyword
Suppose you create a base class named Customer . From that base class you create derived classes named WholesaleCustomer , RetailCustomer , and EducationalCustomer . The base Customer class handles a lot of the expected member data, such as billing address, phone number, sales constants, and other details that customers have in common. However, each derived class has different discounts available. In a situation like this, you know that each derived class needs to calculate its own discount schedule. The base class requires a discount to be applied to each sale, but the details of the discounts must be in the derived classes. The MustOverride keyword provides the answer.
In the base class, you might declare a method like this:
MustOverride Function GetDiscount() As Double
This statement in the base class tells Visual Basic .NET that any class that derives from the Customer base class must provide a GetDiscount() method that returns a Double data type. Note that the base class does not provide its own version of the GetDiscount() function. This is why I use the term declare here, rather than define. You are simply telling Visual Basic .NET to make an entry into the symbol table that declares what data type GetDiscount() returns and enforce the rule that every derived class must provide an implementation of that method.
In other words, the base class declares the method, but the derived class must define (that is, write the code for) the method.
It follows that you must use the following lines in each of the derived classes:
Overrides Function GetDiscount() As Double ' Put the discount code here End Function
The MustOverride keyword enhances the consistency of the class interface because all classes must supply this method.
The Friend Keyword
You learned earlier in this chapter that it's usually a good idea to define the data members in a class by using the Private access specifier . In order to have access to those Private data members outside the class, it is necessary to provide a property access method for each Private data member. This forces the user of the class to go through a consistent interface to gain access to the Private data. Again, this is the idea behind encapsulation.
At the other end of the spectrum, you can give the member of the class a Public access specifier, which makes the data accessible anywhere there is an object of the class. Because such an access specifier runs counter to the basic idea of encapsulation, I tend to minimize its use with respect to the class data members. The Protected access specifier provides an intermediate position from which the base and derived classes gain access to the Protected members and methods.
The Friend keyword provides a slightly different level of access than Protected . Only objects that are defined in the same assembly can access a Friend member. Assembly? For the moment, you can think of an assembly as being similar to a project. A project that you have compiled is one type of an assembly. Also, if you compile a class into a dynamic link library (DLL) file, as in Chapter 15, "Encapsulation and Creating Your Own Classes," that is also an assembly. Friend members are accessible only within the same assembly. In a client/server environment, you might have an assembly that sits on the server with Friend members. A different assembly that sits on the client would not have access to the Friend members on the server.
If you wanted to add a new member to the base Building class called mBuildingID , you might use this:
Friend mBuildingID as String
Now any object of the Building class that is in the same assembly has access to the mBuildingID member. However, mBuildingID is not accessible outside the assembly.
The MyBase and MyClass Keywords
The section "The Overridable and Overrides Keywords" explains how the base class might have a method named ReadPropertyTaxFile() , but a derived class might override it with its own version. Think again about what this means. There are two ReadPropertyTaxFile() methods: one in the base class and another in a derived class.
Now suppose you are in the derived class and suddenly realize that you not only want to use the ReadPropertyTaxFile() method in the derived class, but you now need to use the standard ReadPropertyTaxFile() method in the base class. (Perhaps you want to compare the two results.) Given the scoping rules that apply, how can you call the base class ReadPropertyTaxFile() method from within the derived class?
Simple! All you have to do is prefix the base class method with the MyBase keyword, like this:
This gives Visual Basic .NET an unambiguous reference to the ReadPropertyTaxFile() method in the base class. You can also use the MyBase keyword with data members.
You can use the MyBase keyword in conjunction with members and methods even if it is not necessary. For example, with the three derived building classes described earlier in this chapter, the first line of the DisplayBuilding() method is this, which is defined in the base class and is used to display the base class data members:
You could change that statement to this, and the program would function exactly as it did before:
The only difference from before is that the line now imparts a little documentation information to reinforce that the DisplayBaseInfo() method is found in the base class. Although this change doesn't tell you anything you couldn't find out fairly quickly anyway, it does serve as a memory jogger. For those of us who have drunk from too many aluminum cans over the years , every little bit helps.
The MyClass keyword permits similar behavior to MyBase , but in a different direction. For example, suppose you have defined an Overridable method in a class. This means it is possible for a derived class to override your method definition. By prefixing the method call with MyClass , you ensure that the compiler selects your derived class method rather than the base class method.
For example, if you have a CalculateRents() method in the base class that can be overridden in any derived classes, this statement in your base class code ensures that Visual Basic .NET uses your method, not one from the derived class:
In a way, MyClass is similar to Me , except that it applies to a class and makes Visual Basic .NET see the method calls as nonoverridable.
The NotInheritable Keyword
Visual Basic .NET allows you to build a chain of inheritance classes. For example, you saw earlier in this chapter how the Home class is derived from the base class Building . However, it is also possible to view Home as the base class for new derived classes. These new derived classes from what we might now view as the Home base class might be Ranch and Colonial .
You might decide at some point that this chain of classes has gone far enough and you don't want any further derived classes to be created from the Ranch or Colonial classes. In that case, you would start each class definition with this:
Public NotInheritable Class Ranch . . . End Class Public NotInheritable Class Colonial . . . End Class
If you do this, Visual Basic .NET does not allow any further classes to be derived from these two classes. The NotInheritable keyword ends the inheritance chain.