Inheritance is the concept that a new class can be based on an existing class, inheriting its interface and functionality from the original class.With inheritance, a class gains all the properties and methods that make up the interface of the base class, and it can then extend the interface by adding properties and methods of its own. The new class can also extend or change the implementation of each of the properties and methods from the original base class.
For instance, if we had a class called Fruit that had all the properties and methods which apply to all types of fruit, then inheritance would make it very easy to create classes for Apple, Orange and so forth. We wouldn't have to recreate all the code for an apple to be a fruit: it would get that automatically from the original Fruit class.
Visual Basic 5.0 doesn't directly support the concept of inheritance. It does provide something called interface inheritance using the Implements
keyword. We've already used this keyword when we discussed polymorphism, but it can be applied here to help simulate a form of inheritance.
Interface inheritance lets us inherit the interface of one class into a new class. The downside is that all we get is the interface, not the original object's data or behavior. Worse still, we can't extend an interface created with the Implements
keyword. One of the key benefits of inheritance is the ability to extend the base interface, so this is a substantial drawback.
If we look at a Fruit
class, we'll find an interface that includes elements appropriate for any type of fruit. In this example, we've got a growing climate and color for the fruit:
Option Explicit
Public Property Get GrowingClimate() As String
End Property
Public Property Get Color() As String
End Property
Public Property Get Bites() As Integer
End Property
Notice that there's no code in the routines shown here. That's because all we'll be inheriting is the interface; any code in these routines wouldn't be used anyway.
Now we can create a new class, Apple
, and inherit the interface from the Fruit
class by using the Implements
keyword:
Option Explicit
Implements Fruit
Private Property Get Fruit_GrowingClimate() As String
Fruit_GrowingClimate = "Moderate"
End Property
Private Property Get Fruit_Color() As String
Fruit_Color = "Red"
End Property
Private Property Get Fruit_Bites() As Integer
Fruit_Bites = 20
End Property
Earlier in the chapter, we discussed multiple interfaces and how the Implements
keyword works. Instead of declaring GrowingClimate
, Color
and Bites
directly, we need to make them Private
in scope and put Fruit_
in front of each name so Visual Basic knows that these routines belong to the Fruit
interface.
We might also create an Orange
class, again based on the Fruit
interface:
Option Explicit
Implements Fruit
Private Property Get Fruit_GrowingClimate() As String
Fruit_GrowingClimate = "Warm"
End Property
Private Property Get Fruit_Color() As String
Fruit_Color = "Orange"
End Property
Private Property Get Fruit_Bites() As Integer
Fruit_Bites = 20
End Property
Now, contrary to what we'd expect, we can write code to compare apples and oranges:
Public Function CompareColor(AnApple As Fruit, AnOrange As Fruit)
CompareColor = (AnApple.Color = AnOrange.Color)
End Function
More importantly, we've created two different classes, both having inherited the same interface from Fruit
. This is a very powerful technique when we have otherwise dissimilar classes that have a number of the same interface elements. We can write client code to treat all the objects the same - even though they are different.
Unfortunately, this technique doesn't allow us to extend the interface, and so it is less a way to implement inheritance than it is to implement polymorphism, as in the last section. The Orange
class can't add properties or methods to its version of the Fruit
interface. This limits our flexibility, and really prevents us from using this to simulate full-blown inheritance.
Fortunately, it is possible, with a little extra work, to roll our own inheritance using Visual Basic - and thus gain many of the advantages we'd get in other languages that fully support inheritance. This is achieved by combining a concept called containment with another called delegation.
Containment is the idea that an object can have a private instance of another object inside itself. Were we to have a Human object, it might contain a Heart object. The Heart object would be private to the Human object, since no client code would normally interact with the Heart.
Delegation means that one object delegates work to another object rather than doing the work itself. If our Human object has a PulseRate property, it might ask the Heart object for the rate rather than figuring it out by itself. It has effectively delegated the responsibility for the PulseRate to the Heart object.
If we combine these two concepts, we can simulate inheritance. Let's look at how that can work with our fruit example.
Again, we'll create a class called Fruit
; but this time we'll put some code in the GrowingClimate
routine. With inheritance, we'd expect to be able to use this behavior in any new classes we create based on Fruit
:
Option Explicit
Public Property Get GrowingClimate() As String
GrowingClimate = "Moderate"
End Property
Public Property Get Color() As String
End Property
Public Property Get Bites() As Integer
End Property
Now we'll create an Apple
class, but this time we won't use the Implements
keyword. Instead, we'll manually recreate the interface from the Fruit
class - with absolutely no changes to the declarations of either property:
Option Explicit
Private objFruit As New Fruit
Public Property Get GrowingClimate() As String
GrowingClimate = objFruit.GrowingClimate
End Property
Public Property Get Color() As String
Color = "Red"
End Property
Public Property Get Bites() As Integer
Bites = 20
End Property
The first thing you might notice is that we're creating a Private
Fruit
object within our new class. Our new Apple
class is using the concept of containment to have an instance of the Fruit
class inside itself:
Private MyFruit As New Fruit
In the GrowingClimate
routine, our new Apple
class just delegates the work down to the private Fruit
object:
GrowingClimate = objFruit.GrowingClimate
Now let's create an Orange
class by following the same technique:
Option Explicit
Private objFruit As New Fruit
Public Property Get GrowingClimate() As String
GrowingClimate = "Warm"
End Property
Public Property Get Color() As String
Color = "Orange"
End Property
Public Property Get Bites() As Integer
Bites = objFruit.Bites
End Property
Public Property Get Slices() As Integer
Slices = Bites / 2
End Property
Since the GrowingClimate
for an Orange
is different, it doesn't delegate that call down to the Fruit
object. Instead, it replaces, or overrides, the functionality by implementing the routine itself.
We have also extended the interface for our Orange
object by adding a Slices
property. While we've simulated the inheritance of the GrowingClimate
, Color
and Bites
properties, we're also able to extend the interface as needed for each specific class.
Back to our client code, which compares apples and oranges, we need to change how we accept our parameters:
Public Function CompareColor(AnApple As Object, _
AnOrange As Object)
CompareColor = (AnApple.Color = AnOrange.Color)
End Function
The code within this routine is the same as we had before, but now our parameters are of type Object
instead of type Fruit
. Since our new classes don't use the Implements
keyword, we can't have Visual Basic treat these objects like a Fruit
object.
However, we do know that they have the same set of properties and methods, and so we can write our code to treat them identically. We just need to tell Visual Basic that they are generic objects rather than objects of a specific class.