In “Demystifying version compatibility settings in Visual Basic,” I defined an interface as the collection of public methods exported by an object. This is not an entirely correct definition because an object can have more than one interface. In fact, all Visual Basic objects have at least three interfaces by virtue of their alternate identities as COM objects. Classes can have multiple interfaces as well: That’s the good news. The bad news is that only one of these interfaces, the default interface, is useable at one time by a VB program; the rest are hidden. This is a source of troubles for VB programmers, as I will illustrate in this article.
My simplified definition of an interface actually describes only the default interface—all the public methods defined for a class. Here’s a better definition: An interface is a collection of members defining a set of messages that can be sent to an object.
For an example, let’s assume we have a system composed of one ActiveX DLL and one standard EXE. The DLL component includes a cCustomer class with functions that return a customer’s name and address. The EXE component uses these to process new orders of widgets for a customer. This setup is illustrated in Figure A.
|Version 1.0 of our hypothetical system|
Now suppose we decide to extend our cCustomer class with a phone number property and change our EXE so it’s aware of this new property and is able to use it. We then have the situation illustrated in Figure B.
|Version 1.5 of our hypothetical system|
This all works pretty well. However, rolling out new versions of our hypothetical software is difficult with such a tightly coupled system. We have to be careful not to mix the two versions of our components. In our simple example, we have kept backward compatibility so we can use a v1.5 DLL with a v1.0 EXE, assuming we have our projects’ compatibility settings correct. Of course, the reverse—attempting to run the v1.5 EXE with a 1.0 DLL—would result in a potentially difficult-to-find runtime error, as in Figure C.
|Mixing versions causes a runtime error.|
So we now have to be certain that no users receive the new EXE without receiving the new DLL, as well. Believe me, this is a hassle. Isn’t there an alternative?
Our alternative approach is to create interfaces representing the functionality we need the cCustomer class to support and have our cCustomer class implement those interfaces. That lets us build in a versioning safeguard and gracefully degrade the application’s functionality if an older version of the cCustomer class is being used with an EXE client that expects a newer version. This is accomplished on the client side using the TypeOf keyword, as illustrated in Figure D.
|Our system rewritten to utilize interface inheritance|
Interface inheritance in VB is supported via the Implements keyword, with individual interfaces defined in separate class modules as empty, or abstract, classes composed only of sub, function, and property declarations. When a class implements an interface, it is said to be a concrete class. Concrete classes must provide code (an implementation) for every method defined in every implemented interface. Upon adding an Implements clause to a class module, you’ll see the interface class’s name appear in the left-hand combo box of the module window. Once the interface class is selected in the left combo box, all methods of that class will appear in the right-hand combo box for you to pick out and implement. Don’t worry; they are supposed to be labeled private.
Defining objects in terms of groups of implemented interfaces is a nice way of guaranteeing client compatibility with older versions of the same class. It also provides an incremental method of extending software functionality and is an easy way to ensure that binary compatibility is maintained. But hold on. Before you proclaim me to the highest mountains for sharing these insights with you, there’s a cloud to this silver lining.
The catch is a big one: Remember that VB objects have only one visible interface—the default. VB programs have no built-in way to access any interface other than the default interface of an object, so a programmer is forced to declare a second object of the interface’s class type and use it to manipulate the methods the interface exposes. Thus, the code illustrated in EXE 1.5 in Figure D is actually incorrect (with apologies to those waiting to flame me), would not compile, and should be rewritten as shown in Figure E.
|The correct client code|
The alternatives to the alternative
A possible kludge is to change the scope identifier of each interface method the concrete class implements from Private to Public. Then, the interface methods become methods of the concrete class’s default interface with the interface’s name added to the beginning of the method name. For example Address() would become iPerson_Address().
Another alternative is made possible by the fact that interface classes are type-compatible with their concrete descendants. That’s why the TypeOf comparison and assignment statement in Figure E work. Variables of the concrete type could be passed into a sub or function as arguments of an interface type, with that particular nondefault interface available to be manipulated inside.
Both of the above methods are clumsy and only slightly less onerous than declaring separate variables for each interface. That rains on the parade a bit, doesn’t it? Unfortunately, those are the only choices Visual Basic currently offers us poor developers: Either watch our distributions like hawks to ensure that we have no compatibility problems or perform code gymnastics to use a clumsy interface-based solution. VB.NET promises to fix a lot of this mess with support for “true” inheritance with a single root object. Until that time, we’ll just have to continue to choose the lesser of two evils.
Tell us what you think
Send the editors feedback about this article or about other Builder.com features.