Remember that you have to be very careful when using the words declare and define. The base and derived class code presented thus far in this chapter serve as templates for how Visual Basic .NET will create objects from each of these class declarations.
You cannot do anything with any class until you create an object of the class. At that point, Visual Basic .NET asks Windows for enough memory to create, or instantiate, an object of the class by using the New keyword. Only after that definition occurs in the code do you have an object of the class that can use whatever properties and methods may exist for the class.
When you define an object of a class, what does Visual Basic .NET actually give you? For example, after Visual Basic .NET processes this statement:
Dim MyApartment As New Apartment()
an Apartment object named MyApartment has been defined, but what is it? In Chapter 4, "Data Types and Numeric Variables," you learned that each object variable requires 4 bytes of memory. Really! How can a class object that has its own properties and methods use just 4 bytes of memory? As you know from Chapter 15, the Dim statement actually creates a reference variable named MyApartment . Because MyApartment is a reference variable, it has an rvalue that is the memory address where the actual data for the object is stored. Because a class object's rvalue is always a memory address, only 4 bytes of storage are required for any class object.
After you have defined an object in a program, you need to release the resources associated with the object when you are done using it. Therefore, it's good programming practice to set each object variable to Nothing when you are finished using it. This allows Visual Basic .NET to gracefully free the resources associated with the object.
How Methods Differ from Functions and Subroutines
Encapsulation makes it possible to differentiate class methods from ordinary Visual Basic .NET functions and subroutines. If you write a Visual Basic .NET function or subroutine, the scope of that function or subroutine is equal to that of the assembly in which it is used. That is, it is global to the project it is in. So what?
There is a huge difference between class methods and general functions and subroutines. Because general functions and subroutines have global scope, their names must be globally unique. If you write a function named DisplayBuilding() , you can have only one function with that name in the project namespace. Therefore, if you want that one function to display the class data for different classes, you must increase the complexity of the function so that it can cope with the different display tasks embodied within it. This is not a good thing because the more complexity a routine has, the more chances there are for errors.
Now let's look at class methods. You can have each class use the same method name, and you never need to worry about namespace collision. As shown earlier, encapsulation of the method in the class eliminates this problem because you must prefix the method name with the object name and the dot operator. This removes the namespace problem.
There is another benefit, too. The ability to use the same method name for each class simplifies your programming burden . You can use the same method name in the same manner for all three objects. You don't have a long argument list that has to be memorized because the arguments vary according to the class data to be displayed. Think about all the Visual Basic .NET objects that have Text or Caption properties. Think how hard it would be for us to remember different method names for the Text or Caption properties of every object we used in our programs. That's really scary!
If one class needs a different level of processing complexity in its DisplayBuilding() method, you can place those differences in that class, where they're needed. The fact that one building type in our example needs a different DisplayBuilding() method to display a different class member's list is not a problem. You can tailor each method of the derived class to the unique needs of that class. This means you don't need to bury logic in the method to enable the method to decide which data you are working with, which would be the case for a generalized function or subroutine. Again, this leads to less complex and more easily maintained code. In programming, less is better.
In Listing 17.2, each DisplayBuilding() method begins with a call to DisplayBaseInfo() . (You can see this method code toward the end of Listing 17.1.) The DisplayBaseInfo() method displays the member data of the base Building class. After the information of the base class is displayed, the DisplayBuilding() method displays the member data that is unique to that class.
You can share the DisplayBaseInfo() and FormatMoney() methods from the base class because you have defined them with the Protected access specifier . This makes each of these methods from the base class available for use in the derived classes. (Some other access specifiers are introduced later in this chapter. Stay tuned .)
The remainder of the code in Listing 17.2, for the Commercial and Home classes, is very similar to that for the Apartment class. The only difference is in the member data for each class. Each of the display methods (that is, DisplayBuilding() ) for each class calls the DisplayBaseInfo() method from the base Building class before it displays its own member data. What remains to be shown is the shell that is used to exercise the classes. This is the subject of the next section.