Visual Basic as an Object-Oriented Language
People often say that Visual Basic is not properly object oriented. I would answer that if you were comparing it with C++, you're both right and wrong. Yes, it isn't C++; it's Visual Basic!
C++ is a generic, cross-platform programming language designed around a particular programming paradigm that is, subjectively, object oriented. It is based on and influenced by other programming languages such as Simula, C, and C with Objects.
Visual Basic has evolved-and has been influenced, too-according to system considerations, not primarily to different languages. It was designed to be platform specific; there's not an implementation for the VAX computer, for example. Visual Basic's object orientation, then, is not primarily language based. Its object-oriented language constructs are not there to implement object orientation directly but rather to best utilize the object-oriented features of the operating system-in Windows, of course, this means ActiveX.
ActiveX itself, however, is not a language definition but a systems-level object technology built directly into a specific range of operating systems. It is not subject to committees either, although you might consider this to be a negative point. Additionally, I think I'd call ActiveX and Visual Basic "commercial," whereas I'd probably call C++ "academic." I have nothing against C++ or any other programming language. Indeed, I'm a proponent of mixed-language programming and use C++ almost daily. What I am against, however, is a comparison of Visual Basic with C++. These two languages are as different as COBOL and FORTRAN, and both were designed to solve different problems and to cater to different markets. This all said, I'm still keen to model the world realistically in terms of objects, and I also want to encourage both high cohesion and loose coupling between and within those objects. (Here's a quick tip for the latter: Use ByVal-it helps!) Whether or not I achieve this cohesion and coupling with Visual Basic and ActiveX is the real question.
Cohesion and couplingcohesion and coupling. A component is said to be cohesive if it exhibits a high degree of functional relatedness with other related components. These related components (routines typically) should form cohesive program units (modules and classes). Every routine in a module should, for example, be essential for that module to accomplish its purpose. Generally, there are seven recognized levels of cohesion (none of which I'll cover here). Coupling is an indication of the strength of the interconnections and interactions exhibited by different program components. If components are strongly coupled, they obviously have a dependency on each other-neither can typically work without the other, and if you break one of the components, you'll invariably break the others that are dependent upon it. In Visual Basic, tight coupling typically comes about through the overuse and sharing of public symbols (variables, constants, properties, and routines exported by other units).
What are your intentions?
Having an object implies intention; that is, you're about to do something with the object. This intention should, in turn, define the object's behavior and its interfaces. Indeed, a strong type system implies that you know what you'll do with an object when you acquire one. After all, you know what you can do with a hammer when you pick one up! A sense of encapsulation, identity, and meaning is an obvious requirement. To add both external and procedural meaning to an object, you need to be able to add desirable qualities such as methods and properties. What does all this boil down to in Visual Basic? The class, the interface, and the object variable.
Classes are essentially Visual Basic's way of wrapping both method, which is ideally the interface, and state-that is, providing real type extensions (or as good as it gets currently). A real type is more than a description of mere data (state); it also describes the set of operations that can be applied to that state (method). Unfortunately, methods are currently nonsymbolic. One feature that C++ has that I'd love to have in Visual Basic is the ability to define symbolic methods that relate directly to a set of operators. With this ability, I could, for example, define what it means to literally add a deposit to an account using the addition operator (+). After all, the plus sign is already overloaded (defined for) in Visual Basic. For example the String type supports addition. Also, a solution will have nothing to do with ActiveX; the ability to define symbolic methods is a mere language feature.
Visual Basic "inheritance"
Visual Basic lacks an inheritance mechanism (definitely more of an ActiveX constraint) that is comparable with that of C++. To reuse another object's properties in Visual Basic, you must use something else-either composition or association (composition meaning aggregation-like in a UDT; association meaning, in Visual Basic-speak, an object reference). Historically, association is "late" composition-as a result it also invalidates strong typing. A Visual Basic object cannot, in the C++ sense, be a superclass of another type. In other words, you cannot describe a PC, say, as being either a specialization of or composed of a CPU.
By the way, C++ type inheritance is often used badly; that is, an object that inherits from other objects exports their public interfaces. The result is that top-level objects are often bloated because they are the sum of all the public interfaces of the objects they are derived from-a sort of overprivileged and overfed upper class!
Components that are highly cohesive, yet loosely coupled, are more easily shared-if code reuse is an issue, consider rigorously promoting both of these simple philosophies.
Polymorphism is a characteristic by which an object is able to respond to stimuli in accordance with its underlying object type rather than in accordance with the type of reference used to apply the stimulus. The Implements statement, discussed in the next section, is Visual Basic's way of extending polymorphic types beyond that allowed by the Object type. Polymorphic types might be assigned (or "set") to point to and use one another, and they're useful when you know that one type has the same interface as another type (one you'd like to treat it as). The really important thing is that with polymorphism, an object responds according to its type rather than the type of reference you have to it.
Let me give you an example using two constructs that are probably familiar to you, the Object type and a window handle.
What is the result of this code?
Dim o As Object Set o = frmMainForm MsgBox o.Caption
You can see here that the Caption property evaluation is applied to what the pointer/object reference o points to rather than according to what Object.Caption means. That is, that object to which we bind the Caption access to is decided in the context of what o is set to when o.Caption is executed. (We call this behavior late binding, by the way.) Notice that the code doesn't error with a message that says, "The Object class doesn't support this property or method." Polymorphism says that an object responds as the type of object it is rather than according to the type of the reference we have to it. Again, notice that I didn't have to cast the reference, (using a fabricated cast operator that you might take at first glance to be an array index), like this one, for instance:
The object o knows what it is (currently) and responds accordingly. Obviously, we can alter what o points to:
Dim o As Object If blnUseForm = True Then Set o = frmMainForm Else Set o = cmdMainButton End If MsgBox o.Caption
Again, o.Caption works in either case because of polymorphism.
The second example is a window handle. This window handle is something like the object reference we used above, meaning it's basically a pointer that's bound to an object at run time. The object, of course, is a Windows' window-a data structure maintained by the operating system, an abstract type. You can treat an hWnd in a more consistent fashion than you can something declared As Object, however. Basically you can apply any method call to hWnd and it'll be safe. You're right in thinking that windows are sent messages, but the message value denotes some action in the underlying hWnd. Therefore, we can think of a call to SendMessage not as sending a window a message but rather as invoking some method, meaning that we can treat SendMessage(Me.hWnd, WM_NULL, 0, 0) as something like hWnd.WM_NULL 0, 0. The WM_NULL (a message you're not meant to respond to) is the method name, and the 0, 0 are the method's parameters. All totally polymorphic-an hWnd value is a particular window and it will respond accordingly.
Another similarity between the window handle and the Object type is that a window is truly an abstract type (meaning that you cannot create one without being specific), and so is the Object type. You can declare something As Object- though now it seems what I've just said is not true-but what you've done, in fact, is not create an Object instance but an instance of a pointer to any specific object (an uninitialized object reference). It's like defining a void pointer in C. The pointer has potential to point somewhere but has no implementation and therefore no sense in itself. It's only meaningful when it's actually pointing to something!
I hope you can see that polymorphism is great for extending the type system and for being able to treat objects generically while having them respond according to what they actually are. Treating objects as generically as possible is good; specialize only when you really need to.
OK, so what's the catch? Well, the problem with As-Object polymorphism is that it isn't typesafe. What would happen in my earlier example if, once I'd set o to point to the cmdMainButton command button, I tried to access the WindowState property instead of the Caption property? We'd get an error, obviously, and that's bad news all around. What we really need is a more typesafe version of Object, with which we can be more certain about what we might find at the other end of an object reference (but at the same time keeping the polymorphic behavior that we all want). Enter Implements.
The Implements statement provides a form of interface inheritance. A class that uses the Implements statement inherits the interface that follows the Implements keyword but not, by some magic, an implementation of that interface. A class is free to define code for the inherited interface methods, or the class can choose to leave the interface methods as blank stubs. On the other side of the Implements equation, we can define an interface that other classes can inherit but have nothing to do with a particular implementation.
When you inherit an interface by using the Implements statement, you're providing methods and properties with certain names on an object. Now, when your containing class is initialized, it should create a suitable implementation of these promised methods and properties. This can happen in one of two ways:
- Pass method and property invocations on to the underlying implementation (by creating and maintaining a local copy of an object that actually implements the methods and properties). This mechanism is often called forwarding.
- Handle the invocation entirely by itself.
How does this differ from As-Object polymorphism? Basically, when you set a typed object reference to point to an object, the object instance to which you set it must implement the interfaces specified by the type of the object reference you use in the declaration. This adds an element of type safety to the dynamic typecast, which is what you're implicitly doing, of course.
When you choose not to implement an interface other than by forwarding requests to the underlying base type (the thing you've said you implement) you can get these problems:
- The derived class now acts like a base class-you've just provided a pass-through mechanism.
- The base object reference is made public in the derived class (by accident), and because you cannot declare Const references, the reference to your implementation might be reassigned. (Actually, it's just as easy to do this via composition in Visual Basic.)
A consequence of the second problem is that you can accidentally "negate" the reference to the base object. Say, for argument's sake, that you set it to point to a Form; clearly the derived class no longer implements a base but a Form. Better hope your method names are different in a base and a Form or you're in for a surprise!
It's important to realize that Implements can be used with forwarding or via composition. The only difference in Visual Basic is the keyword New-in fact, it's even grayer than that. In class Derived, does the following code mean we're using forwarding or composition?
Implements Base Private o As New Base
Do we contain a Base object or do we simply have a variable reference to a Base instance? Yes, it's the latter. In Visual Basic you cannot compose an object from others because you cannot really define an object-only an object reference.
Let me summarize this statement and the meaning of Implements. When you're given an object reference you have to consider two types: the type of the reference and the type of the object referenced (the "reference" and the "referent"). Implements ensures that the type of the referent must be "at least as derived as" the type of the reference. In other words, if a Set works, you have at least a reference type referent object attached to the reference. This leads us to the following guarantee: If the class of the reference has the indicated method or property (Reference_Type.Reference_Method), then the object reference-the referent-will have it, too.
Delegating objects consists of two parts. Part one deals with who responds-through my interface I might get an actual implementor of the interface (the object type I have said that I implement) to respond (possibly before I respond), or I might elect to generate the whole response entirely by myself. Part two deals with who is responsible for an object; this is used in conjunction with association. Two containers might deal with a single provider object at various times. This use of association raises the question of object ownership (which container should clean up the object, reinitialize and repair it, and so forth).
Object orientation is modeling the requirements. Defining the requirements therefore dictates the object model and implementation method you'll employ. You can build effective sets of objects in Visual Basic, but you cannot do today all of what is possible in C++. As I said earlier, Visual Basic and C++ are two different languages, and you should learn to adapt to and utilize the strengths of each as appropriate.