Enhancing public classes
When you create a public COM class or control, you are signing a contract never to change that component in any way that would break existing clients. I’ll talk more about this in Chapter 10. In real life, things change, contract or no contract. The convention for creating a new class that is a superset of an existing class is to give it the same class name, but to append a digit. For example, if your main client, Big Brother And Company, complains that your CMotivate class just isn’t doing the job, it might be a good idea to enhance it by creating the CMotivate2 class.
First, let’s check CMotivate:
‘ CMotivate
Function Cheer() As String
Cheer = “Rah, rah!”
End Function
That’s OK, but maybe Big Brother wants to try some different incentives. Since you don’t want to rewrite the whole class from scratch, you can use delegation to reuse the existing part of the class and add new features in CMotivate2:
‘ CMotivate2
Private motivate As New CMotivate
Function Cheer() As String
Cheer = motivate.Cheer & “ Rah!”
End Function
Function Threaten() As String
Threaten = “Shape up!”
End Function
This class contains a CMotivate object, which it uses to provide the exist-
ing functionality. It enhances the existing Cheer method and adds a Threaten method. The technique of including an object in a class and using that object’s features to enhance the new object is called containment—your outer object contains an inner object to which the outer object delegates part of the work. We’ll be seeing a lot more delegation in this book—too much, in fact. We’ll have to use delegation for tasks that would be done with inheritance in other object-oriented languages.
The CMotivate2 class does everything CMotivate does and more, but it doesn’t do that work in the same contexts. For example, Big Brother might want to pass a CMotivate2 object to their Motivator function. Motivator was written by Big Brother and we have no control over it. They are perfectly satisfied with Motivator and have no intention of changing it. But they want the other benefits of the CMotivate2 class. Motivator looks like this:
Function Motivator(motivate As CMotivate) As String
Motivator = motivate.Cheer
End Function
The client wants to use it like this:
Dim motivate2 As New CMotivate2
Debug.Print motivate2.Cheer
Debug.Print motivate2.Threaten
Debug.Print Motivator(motivate2)
‘Error! Fail because motivate2 is not a CMotivate.
You receive a nasty note from Big Brother telling you that if they’re going to have to rewrite all their programs, they might as well rewrite them with classes from some other vendor. You assure them that the CMotivate2 error was a fluke and that the CMotivate3 class will not only be compatible with CMotivate, but will also add more new features. In real life, CMotivate3 would have to be compatible with both CMotivate and CMotivate2, but to keep things simple, we’ll pretend CMotivate2 was a beta version that was never deployed and that Big Brother doesn’t need compatibility with it.
To be truly compatible, the CMotivate3 class needs to use the Implements statement to make the new class polymorphic with CMotivate. The methods and properties of the interfaces we’ve implemented so far have been empty, but that’s not a requirement. In fact, every class you define has an interface, and in a sense, is an interface. IFilter is an interface by convention. Technically, it’s an ordinary class that happens to have empty members and a name that follows the interface convention. Under the hood, IFilter has a real COM interface called _IFilter, but the Visual Basic Implements statement lets you use the class name to access the interface. It doesn’t matter whether the implemented methods have their own implementation.
In object-oriented languages with inheritance, virtual functions are frequently given base functionality in the base class. Other classes inherit the base functionality and extend it. Since the functions are virtual, the extended class can work polymorphically. You’ll find it difficult to do this with Visual Basic because it lacks inheritance, protected members, and other common features of object-oriented languages. Instead, you must use delegation to fake inheritance. Here’s how the CMotivate3 class does it:
‘ CMotivate3
Implements CMotivate
Private motivate As New CMotivate
‘Delegate to internal CMotivate object
Private Function CMotivate_Cheer() As String
CMotivate_Cheer = motivate.Cheer & “ Rah!”
End Function
‘Reuse inner implementation for outer method
Function Cheer() As String
Cheer = CMotivate_Cheer
End Function
Function Threaten() As String
Threaten = “Shape up or ship out!”
End Function
Function Bribe() As String
Bribe = “Cash under the table!”
End Function
By implementing the CMotivate_Cheer method with delegation, CMotivate3 provides an enhanced version of the Cheer method for CMotivate clients. It must provide a separate Public Cheer method for CMotivate3 clients, but it can delegate the work to CMotivate_Cheer.