Is Visual Basic Object Oriented?

[Previous] [Next]

Yes. Although object-oriented purists might disagree, I found that Visual Basic met the definition of an object-oriented language as of version 5. I will admit, however, that there is definitely room for improvement. In this chapter, I will identify where Visual Basic comes up short and suggest the language features that Microsoft could add to make Visual Basic more robust. In addition, I will provide workarounds to these limitations where possible.

Visual Basic is object oriented because it supports the three fundamental principles of object-oriented methodology: encapsulation, inheritance, and polymorphism. Let's take a closer look at each principle in the following sections.

Encapsulation

Encapsulation is often referred to as "information hiding." It is the ability of a class object to restrict client access to that class object's internal representation (data and functions). The following sections provide an in-depth description of four methods by which you can enforce encapsulation: private class members, private static class members, friends, and private helper class objects.

Private Class Members

Encapsulation is accomplished by defining member variables and functions of a class as private. You can assume that class member functions (also known as methods) defined as public internally manipulate private member variables and functions. In object-oriented terms, the public methods of a class are an abstraction of the class's private representation. The result is that the private representation is encapsulated.

The advantage of encapsulation is that you can reimplement the class to contain different types of private member variables and functions, and you can update the implementation of the public methods as required (provided that the function signatures of the public methods don't change). For example, you might find that changing the type of a private member variable from a Collection to an array will significantly increase the performance for a given type of object. Because the Collection variable is private to the class, you can redefine its type and update the implementation of the public methods that use the variable without breaking client collaboration.

Client/Server Object Relationships

A client is an application, object, or component that requests a service from another object. The object receiving the request exposes available services via its public methods. Hence, this object is the server in this relationship because it is providing a service. An object can be a server to one object and a client of another object simultaneously, which models the real world. For example, you (the reader) are a client of mine (the author), and I am providing you this information service. In turn, I am a client of other authors who have published books that I have used during the research stages of this book.

Private Static Class Members

A standard class feature that supports encapsulation in most object-oriented languages is the ability to define static class members. Static member variables and functions are members of the class rather than members of class objects. A static member is created once for the class and is global to all instances of that class. Static members can be defined as private, so encapsulation is enforceable. Static members allow you to reduce the overhead required to maintain separate instances of members that can be shared by all class objects. Helper functions (described later in the section "Private Helper Class Objects") are excellent candidates to be declared as static, because they are private, implementation-specific functions that further modularize the internals of the class by enhancing code readability, reuse, and maintainability within the confines of the class implementation. A static member is also the perfect mechanism for sharing data between objects of the same class—it does not fall into a program's global name-space, so naming conflicts between other variables or functions defined outside its class will not occur.

The static class member feature is not directly supported in Visual Basic. Each instance of a Visual Basic class gets a separate copy of the class's members. I think Microsoft should, without a doubt, add this feature to Visual Basic. In any case, let me illustrate an alternative that provides the equivalent effect, albeit with certain limitations.

Public functions and variables defined in Visual Basic modules that are included in an ActiveX component project (such as a DLL, an EXE, and so forth) are global within that ActiveX component project but are private to external clients of the component. So, if your intention is to package related classes in a class library or to build a framework of collaborating class objects, you could receive similar results to static declarations by distributing your library or framework as an ActiveX component. Yes, all objects of classes defined within the project have access to these public variables, but if programmers maintain their professional etiquette, programmers of other classes in the same project will not violate the intended use of public variables or functions for their own benefit. I know I'm making a rather weak argument for using this ActiveX alternative to static declarations, but the ability to use class modules in this capacity does allow me to turn my object-oriented purist head the other way. In spite of this limitation of global access within a project, the issue of not being able to declare static methods is mostly solved because class overhead is reduced, information can be shared between class objects in the same component, and the public variables and functions are encapsulated by the component. The clients of the ActiveX component, therefore, are not aware of the existence of these public variables and functions.

Friends

Most of us maintain a certain facade that we expose to the public at large, which we can refer to as our public interface. (In the world of programming, an interface is a group of functions—more on this in the section "Types of Inheritance.") People's perceptions and interactions are determined by what they expose; other information is kept private and is not accessible through the public interface. In this manner, class objects are an exact model of the real world. All that is known about a type of object is exposed through its public properties, methods, and events. What is not known about a type of object is defined as private.

We all know that we're not supposed to keep too much personal information bottled up inside, so we confide in a friend and give them access to our private information. The same scenario is true for objects. One type of object can be a friend to another type. In fact, as in human friendships, the trust might be only one way. Each party (type of object) in the relationship individually decides whether to accept the other as a friend.

Each object-oriented programming language implements friends somewhat differently, but ultimately, each language provides the same result—the ability to access the private members of a class object by invoking a friend function. Personally, I favor the C++ implementation.

A friend in C++ terminology is a nonmember of a class (meaning it doesn't have access to nonpublic members of the class unless they are friends) that has access to another class's nonpublic members. (We say nonpublic rather than private here because unlike Visual Basic, C++ supports different levels of privacy.) C++ friends can be stand-alone functions, member functions of another class, or an entire class. Making an entire class a friend of another class is a shortcut to separately defining all class functions as friends of another class. It's clear that friends are public—the implementation of a friend function makes it possible to access the private members of another class object.

Visual Basic's implementation of friends is similar to its quasi-implementation of static class members. Friends, like static members, make sense only in an ActiveX component project because all classes within a component are friendly. You are therefore faced with the dilemma of open access to class members. Again, some professional etiquette will be necessary, or you'll have to include in an ActiveX component project only those classes and class members that should be friends, which might prove to be impractical.

Unlike in C++, friends in Visual Basic are class members that are publicly accessible to all classes of objects in the ActiveX component but that are not accessible to external clients of the component. As a result, friends in Visual Basic are not compiled as part of a class's public interface in a type library. Both early binding and late binding techniques of accessing friends from a client will result in error. For example, assume that AnnualIncome is a friend property of the class Person, which is defined in the PersonalFinances ActiveX DLL, and that you wrote the following client code:

 Dim thePerson1 As Object Dim thePerson2 As PersonalFinances.Person Dim amount As Double ' Using late binding Set thePerson1 = CreateObject("PersonalFinances.Person") ' Run-time error amount = thePerson1.AnnualIncome ' Using early binding Set thePerson2 As New PersonalFinances.Person ' Compile-time error amount = thePerson2.AnnualIncome 

Because the client code above is not within the context of the PersonalFinances ActiveX component, it cannot reference the friend property AnnualIncome of the Person class. A client of the ActiveX component can access only the public members of class objects defined within the component. That being the constraint, the approach I would take as the component developer would be to define other classes in the component that by default are friends of the Person class. For example, within my component I could define a FinanceCoordinator class that has a public method for retrieving a member of the Person class, and an Accountant class that has a public method for determining whether a given person's annual income falls within a particular range. The client code could then read as follows:

 Dim theFinCoord As PersonalFinances.FinanceCoordinator Dim thePerson As PersonalFinances.Person Dim theAccountant As PersonalFinances.Accountant Set theFinCoord = New PersonalFinances.FinanceCoordinator Set thePerson = theFinCoord.GetPerson("John Smith") Set theAccountant = theFinCoord.GetAvailableAccountant If theAccountant.ClientSalaryRange(thePerson, OVER_50K) Then ' Give the loan to the candidate.  Else ' Deny the loan to the candidate.  End If 

The point I'm trying to convey here is that a friend is someone who has access to private information about you. To translate, member functions of one class object have access to private members of a different class object. The theAccountant object of type Accountant is a friend of the thePerson object of type Person. Because the previous code extract belongs to a client that is not in the PersonalFinances ActiveX component, the client is not a friend of the Person class, so it cannot ask the thePerson object what its annual income is. Indirectly, however, the client can find out the information it needs by interacting with the theAccountant object. Visual Basic does not implement friends as I described at the beginning of this section in our discussion of C++ friends. Friends in Visual Basic are simply properties or methods of a class that are public within an ActiveX component and private outside the ActiveX component. Here is a code extract of what you might expect to find in the PersonalFinances ActiveX component project.

 ''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Project: PersonalFinances (ActiveX DLL) ' Class Name: Person ' Private m_AnnualIncome As Double  Friend Property Get AnnualIncome() As Double AnnualIncome = m_AnnualIncome End Property ''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Project: PersonalFinances (ActiveX DLL) ' Class Name: Accountant '  Public Enum SalaryRange UNDER_20K BETWEEN_20K_AND_50K OVER_50K End Enum Public Function ClientSalaryRange(Psn As Person, _ SR As SalaryRange) As Boolean Select Case SR Case UNDER_20K ClientSalaryRange = Psn.AnnualIncome < 20000 Case BETWEEN_20K_AND_50K ClientSalaryRange = (Psn.AnnualIncome > 20000 And _ Psn.AnnualIncome < 50000) Case OVER_50K ClientSalaryRange = Psn.AnnualIncome > 50000 End Select End Function 

As illustrated in this code extract, AnnualIncome is a friend property defined in the Person class that returns the value of the privately defined m_AnnualIncome member variable. Defining this property as a friend makes it accessible to the Accountant class that is defined within the same component. The Accountant class contains the ClientSalaryRange public method (member function) that determines whether a given Person object's salary falls within the specified range by referencing the AnnualIncome friend property. Now let's say I added another class, named StockBroker, to the component project. StockBroker class objects will also be friends of Person class objects, which gives the StockBroker class objects access to the AnnualIncome friend property. A client of this component cannot access the AnnualIncome friend class members, but it can access public methods such as ClientSalaryRange.

While enforcing encapsulation, friend relationships allow the collaboration of two or more class objects that would otherwise be impossible based on the information made available through their public interfaces. Even objects of the same class (type) cannot access each other's private members. Friends allow this type of encapsulation so that you can determine such information as whether two objects of the same class are in the same state. In this case, the client code could resemble the following:

 Dim p1 As Portfolio Dim p2 As Portfolio Set p1 = New Portfolio Set p2 = New Portfolio ' Process portfolios.  ' Compare portfolios. If p1.Equals(p2) Then  End If ' It is commutative; therefore, you could also ' get the same result from this code. If p2.Equals(p1) Then  End If 

A friend relationship is a clever concept to employ, but I suggest you use it sparingly. Although friends do not violate laws of encapsulation between class objects in an ActiveX component and clients of the component, if they're used as intended, friends can be a direct violation of encapsulation between class objects within an ActiveX component. As a result of creating friends, a direct dependency between class objects is created, making it difficult to change the private implementation of a class without adverse effects.

Private Helper Class Objects

As mentioned, helper functions are private member functions of a class that add significant value to the implementation of a class by modularizing code that can be reused by other elements of the implementation. Also, implementation code is easier to maintain and is more readable as a result. Why not take the helper function concept a step further by creating helper classes designed to support the implementation of another class?

The goal is to create a helper class object that is intended to be used solely within the context of a single class implementation, so the helper class should be defined as private within the single class that it supports. Ideally, Visual Basic would allow you to define nested classes similar to the following code:

 Class TelephoneService Private: Class FiberOptics Public: Function ConfigureWireProtocol(...) As Boolean ' Configuration code End Function End Class Static m_FiberOpt As FiberOptics = New FiberOptics Public: Sub MakeACall(...) ' Do some stuff and call the FiberOptics ' helper class object to further facilitate ' this implementation.  retcode = m_FiberOpt.ConfigureWireProtocol(...)  End Sub End Class 

The TelephoneService class is scoped globally. Therefore, a client process can instantiate objects of type TelephoneService, followed by calls to the publicly defined method MakeACall. Notice, however, that the FiberOptics class is private to the TelephoneService class. This implies that FiberOptics is a helper class designed to support exclusively the implementation of the TelephoneService class. Following the FiberOptics class definition, I defined and initialized a static TelephoneService class member variable named m_FiberOpt of type FiberOptics. The m_FiberOpt variable is a member of the TelephoneService class rather than a member of TelephoneService class objects. Hence, it is created once, and then is globally available to all TelephoneService class objects. But since it is private to the class, it is inaccessible from a client of a TelephoneService class object.

In short, a helper class is similar to a helper function in that it should be defined as private to the class it supports. Also, an object of the helper class type should be defined as private to the class it supports. Consequently, encapsulation is fully enforced, which allows developers to enhance the implementation of a class that now includes helper class objects without concern for breaking collaboration with clients. Because a helper class is for use exclusively with a single class, changing its public interface requires changes only in the implementation of the class it supports, which is the only user dependent on its interface. Code changes in this situation are easily maintainable.

Unfortunately, Visual Basic does not support the concept of helper classes or static class members as just described. In fact, the code extract in the previous example is not legal Visual Basic code—my intention is to show you what it could be. Visual Basic does not support the idea of nested classes. For that matter, you cannot define multiple classes in the same file in Visual Basic. As you know, there is a class module type in Visual Basic that uses the same editor as the standard and form modules. The Class…End Class syntax in the example also is not legal. The most reliable way to determine whether you are looking at the code of a class, a form, or a standard module is to view the Properties window. You will see in the Properties window object box the name of the module in bold, followed by the type of module in normal font style. A class module type is ClassModule. (See Figure 2-1.)

Similar to private static class members and friends, you can define private helper classes despite the missing language features, provided you are working within an ActiveX component project. As shown in the scenarios given earlier, you can define classes that are public to all classes in the component but that are private to clients of the component. To enforce this scope, use the Properties window to set the Instancing property value of a class module to Private. (See Figure 2-1.)

Figure 2-1. Use the Properties window for changing the Instancing property value of a given class module.

The following code extract, rewritten from the previous code extract—this time in legal Visual Basic syntax—produces results equivalent to what we would have hoped for from our illegal syntax. Keep in mind that this is possible only in an ActiveX component project:

 ''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Project: TelephoneSvr (ActiveX DLL) ' Class Name: TelephoneService ' Note: Component class accessible by clients of ' the component. ' ' This class's Instancing property value is ' set to MultiUse, which is the default of ' an ActiveX DLL component project.  Public Sub MakeACall(...) ' Quasi-static class member variable g_FiberOpt ' is defined as public in a standard module in ' this component project. It therefore has a ' global scope within the component, but is ' inaccessible to clients of the component. If g_FiberOpt Is Nothing Then Set g_FiberOpt = New FiberOptics End If ' Do some stuff and call the FiberOptics ' helper class object to further facilitate ' this implementation.  retcode = g_FiberOpt.ConfigureWireProtocol(...)  End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Project: TelephoneSvr (ActiveX DLL) ' Class Name: FiberOptics ' Note: Helper class of the TelephoneSvr ActiveX ' component, designed to exclusively support ' the TelephoneService class implementation. ' ' This class's Instancing property value is ' set to Private via the Properties window.  Public Function ConfigureWireProtocol(...) As Boolean ' Configuration code End Function 

From the point of view of an object-oriented purist, the code above illustrates that private helper class objects are possible in Visual Basic; however, the code's implementation falls short because, like static class members and friends, all classes within a component have access to helper classes. So the scope is the ActiveX component instead of the class, which results in a violation of encapsulation within the component. You cannot guarantee that a single class implementation is the only client of a specific helper class. Changing the interface of a helper class in any way could lead to a code maintenance nightmare if numerous classes in a component reference the same helper class. In place of the missing language features, those who develop a component should remember to maintain good object-oriented etiquette. On the other hand, clients of the component are unaware of the existence of helper classes, so encapsulation is enforceable on the client level, making this feature worthy of implementation despite its shortcomings.

By now you have probably noticed a recurring theme. Visual Basic effectively supports even the most advanced features of object-oriented programming, provided you are developing an ActiveX component of some sort. Hence, it seems almost an accident that Visual Basic's support for object-oriented programming works the way it does. Keep reading—I will eventually share some insights that will affirm that impression.

Inheritance

When you create an object-oriented programming solution, you hope to gain reusability as an end result. Instead of reinventing the wheel every time in the context of a new system, you should make classes of objects available for reuse. At times, however, having access to the public properties, methods, and events of a class isn't enough for effective reuse, so you have to make a few choices. First, you could simply redefine a private class member as public so that another class could obtain the interaction it needs. Second, you could establish friendships between classes in an ActiveX component by defining class properties and methods as friendly (using the keyword Friend. A friend preserves encapsulation by providing other, friendly classes with access to its private members. Third, a class could inherit the characteristics of other classes.

Concrete and Abstract Classes

A class is another name for a type of something. With respect to object-oriented concepts, that "something" is an object. A class defines the characteristics of an object, which include its structure and behavior. Objects are constructed based on a class (or type). Said another way, an object is an instance of a class.

Classes can either be concrete or abstract. A concrete class defines something that is tangible. Hence, an object can be constructed based on this class. For example, a Porsche Carrera object can be constructed based on an Automobile class, because Automobile defines the characteristics of something concrete.

An abstract class, on the other hand, defines a concept that in and of itself can not be materialized unless applied to something concrete. For example, color, shape, and sound are abstract terms. Conceptually you know what they are because someone pointed out a concrete object that exhibited those characteristics. If I were to say to you, "Give me a square," the first question you would probably ask me is, "A square what?" Objects, therefore, cannot be constructed based on an abstract type. Concrete classes can, however, inherit the characteristics of an abstract class. For example, an Automobile class could inherit color, shape, and sound from an abstract class. Now, based on the Automobile class, you can construct a silver Porsche Carrera object that produces a deep throttling sound.

Visual Basic supports the concept of an abstract class in ActiveX component projects only. Provided you have at least one class in the component project that is creatable by an external client, you can create an abstract class by adding a new class module to the ActiveX component project. Next you can add public properties and methods with no implementation. You do this by changing the class's Instancing property value to PublicNotCreatable in the Properties window. Clients external to this component cannot create instances of this class by using the New keyword or the CreateObject function. However, classes defined within the context of the client can derive concrete classes from the abstract interface.

Be aware that classes within the component can create instances of this class and pass references to it back to an external client, which violates the intended abstract nature of the class. I consider this to be one of the many indications that this feature is really there for reasons determined by the underlying ActiveX/COM technology, which I will shed some light on in the following chapter. It is only by accident that a class defined as PublicNotCreatable serves as a means by which to enforce object-oriented principles.

Types of Inheritance

Inheritance is the act of deriving one class from another. Public inheritance implies that the descendant class is a subclass or subtype of its ancestor class. For example, if class Poodle and class Wolf derive from class Canine, they are both types of Canine. This is referred to as an "is a" relationship (as in, a Wolf "is a" Canine).

Nonpublic inheritance implies that the descendant class is not a subclass of its ancestor, but that it only inherits its characteristics. For example, if you create a Map collection class, you might want to privately inherit from an Array class. Your reasoning might be that a Map collection is not a type of Array, but the Array class has some storage functionality that you would like to reuse in your Map class. This type of inheritance enforces encapsulation and is similar to the composition of one class object being a private member of another. This is referred to as a "has a" relationship (as in, a Map "has an" Array).

Inheritance can be further categorized into two major forms: implementation inheritance and interface inheritance. Implementation inheritance supports the concept of a class hierarchy, where one class can derive from another class, potentially inheriting the structure and behavior of all its ancestors. The changes from the ancestor to the descendant are limited only by the capacity of the current technology. A descendant class might choose to override the behavior of certain public methods of its ancestor and leave others untouched. This form of inheritance might also permit access to nonpublic class members of an ancestor to its descendants. Unfortunately, Visual Basic currently does not support implementation inheritance.

Interface inheritance is the ability of one class to inherit the interface of another. An interface is a group of class property and method signatures declared as public. In Visual Basic, class properties and methods are defined inline, so the term "signature" might seem a bit confusing to a Visual Basic programmer. A property or method signature is the name of the property or method, followed by a list of parameters and a return type, if applicable. The signature does not include implementation code.

Visual Basic supports multiple-interface inheritance. Regardless of whether a class is abstract or concrete, one class can inherit the interfaces of many other classes.

The Implements Keyword

At the top of a class module, add the keyword Implements, followed by the name of the interface the class is implementing. The keyword Implements accurately describes what a class is about to do—provide implementation for the properties and methods for the interface from which it is deriving. To derive a class from multiple interfaces, add an Implements entry for each interface on a separate line in the class module. Keep in mind that the deriving class must implement every property and method of the specific interface. If your intention is to implement part of an interface, at a minimum you must select the remaining methods from the Procedure drop-down list in the top right-hand corner of the class module editor (as shown in Figure 2-2) to put an empty implementation of the method into the class module. I see this type of implementation as bad programming practice. If you are not implementing an entire interface, you should probably not derive your class from it. Interfaces are considered binding contracts, because a client that expects a specific interface is guaranteed that the object it intends to reference fully supports all properties and methods defined in that interface. Here is a code extract of what you might expect to find in a Visual Basic program.

 ''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Project: CanineExhibitSvr (ActiveX DLL) ' Class Name: Canine ' Note: Interface that defines characteristics ' of the canine species. ' ' Notice there is no implementation defined in this ' class module, just public properties and methods ' with empty bodies. ' ' This class's Instancing property value is set to ' PublicNotCreatable via the Properties window. ' ' Note: At least one class in an ActiveX component ' project must have an Instancing property ' value that allows it to be publicly creatable.  ' ' Properties '  ' ' Methods ' Public Sub Bark() End Sub Public Sub Eat() End Sub Public Sub Run() End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Project: CanineExhibitSvr (ActiveX DLL) ' Class Name: Wolf ' Note: Concrete class that inherits the Canine ' interface. ' ' Notice all interface methods are implemented. ' ' This class's Instancing property value is set ' to MultiUse via the Properties window. ' ' Concrete class Wolf inherits the Canine abstract interface. Implements Canine  ' ' Implementation of the Canine interface ' Private Sub Canine_Bark() ' Implementation code goes here. End Sub Private Sub Canine_Eat() ' Implementation code goes here. End Sub Private Sub Canine_Run() ' Implementation code goes here. End Sub 

NOTE
The boldfaced code in this extract indicates that these are the implementations of the Canine interface when you use the keyword Implements.

An external client of the CanineExhibitSvr ActiveX component cannot create an instance of type Canine. But the external client could either define a new concrete class that derives from Canine or create an instance of the concrete class Wolf that exhibits all the characteristics of Canine.

click to view at full size.

Figure 2-2. You can use the Procedures drop-down list in the class module editor to implement each method in the class.

Implementation Inheritance

Although not supported by Visual Basic, implementation inheritance has benefits worth reiterating, and later in this section we look at how to gain those benefits in Visual Basic.

In implementation inheritance, when a class publicly inherits from another, the descendant class does or acts as the following:

  • It is a subclass (or subtype) of its ancestor. This plays a key role in polymorphism, which is discussed in the next section.
  • It not only inherits all the characteristics of its immediate ancestor but potentially those characteristics of all its ancestors in the descendant chain that came before it.
  • It can override behavior for the methods defined in its ancestor.
  • It can have access to nonpublic members of its ancestors when available.

Implementation inheritance has two significant drawbacks. The first is that if the programming language you are using supports multiple-implementation inheritance, as C++ does, it is likely that eventually you will derive a class from two or more other classes that have the same ancestor. This introduces an ambiguity that should be caught by the compiler. Essentially, the compiler is unable to anticipate your intended usage. For example, as the class diagram in Figure 2-3 illustrates, you might want to maintain separate ancestor instances. Notice that class A is an ancestor of classes B and C, both of which class D is derived from.

Figure 2-3. An example of multiple-implementation inheritance showing that a class can be derived from classes that have the same ancestor.

Unless the programmer implements specific code changes, ambiguity will exist because it's not clear which class A ancestor should ultimately respond to an invocation initiated by class D. There are other programming changes that entail classes B and C sharing a single class A instance or that replace multiple inheritance with a combination of single inheritance and composition. The point is that it's not a trivial problem to solve. Since Visual Basic does not yet support implementation inheritance, let's just leave it as food for thought.

The second drawback of implementation inheritance is the amount of memory that a descendant class might require, depending on where the class falls in the class hierarchy. When a descendant class object is created, its entire ancestral line is created along with it. Otherwise, how else would a descendant reuse the functionality that it inherited from its ancestors? The compiler will not complain about the additional memory because, of course, memory use is not an error. So, it is up to the programmer to understand the effects of implementation inheritance on memory usage.

Interfaces in Visual Basic cannot derive from one another; therefore, it is impossible to create an ambiguous situation due to common ancestors. Also, the only overhead resulting from the derivation from an interface is in the derived class's implementation. You don't have to worry about a descendant chain of classes.

All the benefits of implementation inheritance can be gained in Visual Basic by using interface inheritance along with a few other innovative techniques. For example, in a PetshopSvr ActiveX component, I have created a GermanShepherd class that must derive from the concrete DomesticDog class. The DomesticDog class interface consists of three public methods: Bark, Eat, and Run. By default, I will accept the Run and Eat methods as implemented in DomesticDog, but I want to override its Bark method because a GermanShepherd object's bark has deeper undertones than the average DomesticDog object's bark.

The GermanShepherd class inherits the DomesticDog class interface through the use of the keyword Implements. Then the GermanShepherd class implementation of the DomesticDog interface's Eat and Run methods delegates their tasks to the private DomesticDog object member variable composed in the GermanShepherd class. However, the GermanShepherd class will override the Bark method of the DomesticDog class interface by providing its own implementation that does not delegate back to the private DomesticDog object member variable. The class diagram shown in Figure 2-4 is an accurate depiction of interface inheritance coupled with composition to gain results that are equivalent to implementation inheritance. The following is the Visual Basic source code you might expect to find in the PetshopSvr ActiveX component project.

 ''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Project: PetshopSvr (ActiveX DLL) ' Class Name: DomesticDog ' Note: Concrete class  ' ' Properties '  ' ' Methods ' Public Sub Bark()  ' Implementation code goes here. End Sub Public Sub Eat()  ' Implementation code goes here. End Sub Public Sub Run() ' Implementation code goes here. End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Project: PetshopSvr (ActiveX DLL) ' Class Name: GermanShepherd ' Note:  Derived from the concrete class DomesticDog '   '   ' Inherit interface. Implements DomesticDog  ' Compose private DomesticDog object member variable. Private m_domesticDog As DomesticDog ' When an instance of this GermanShepherd class is created, ' auto-construct an instance of DomesticDog and assign a ' reference to it to the private DomesticDog object ' member variable. Private Sub Class_Initialize() Set m_domesticDog = New DomesticDog End Sub ' Implementation of the DomesticDog interface ' Private Sub DomesticDog_Bark() ' GermanShepherd implementation that produces ' the bark required  End Sub Private Sub DomesticDog_Eat() ' Delegate task to private DomesticDog object ' member variable. m_domesticDog.Eat End Sub Private Sub DomesticDog_Run() ' Delegate task to private DomesticDog object ' member variable. m_domesticDog.Run End Sub 

Interface inheritance is the only type of inheritance that is absolutely necessary for object-oriented programming in Visual Basic. Interfaces define an object's role in a system. The majority of the design patterns described in this book explain how to create interfaces implemented by classes that work together to solve a specific set of problems. I do provide full implementations of these interfaces; however, the point that will become evident is that the interface, more so than the class implementation, determines the scalability, reusability, and extensibility in a given system.

Figure 2-4. Interface inheritance coupled with composition allows the German-Shepherd class to inherit the characteristics of the DomesticDog class.

Polymorphism

It is often said that an object variable in Visual Basic is actually an object reference to a type of object. This is not 100 percent accurate. A more precise definition would be that an object variable is a reference to a type of object that supports a specific interface. As described in the previous section on inheritance, one type (class) of object can support one or more interfaces. By using the keyword Implements followed by a valid interface name, a class can provide its own implementation for all properties and methods defined in an interface. Because an object is an instance of a specific class, it therefore supports the interfaces implemented by the class to which it conforms.

A Visual Basic class always has at least one interface that is referred to as its default interface. For the Visual Basic programmer, the default interface is the class itself. All public properties and methods defined in a class become part of its default interface.

Interfaces are not classes. Interfaces are unique identifiers for a group of property and method signatures. Classes define implementations for all properties and methods of an interface that they support. More than one class can support the same interface. Each class, however, will have its own unique implementation. On that note, Visual Basic supports two types of polymorphism, which I refer to as smart polymorphism and oblivious polymorphism.

Smart Polymorphism

Smart polymorphism occurs when you declare an object variable that can be assigned a reference to any type (class) of object that supports the interface the object expects. The resulting invocations of this object variable might produce different behavior depending on the object each invocation references. For example, say the following code fragment is defined in a project. The compiler will permit this code provided the project containing it either defines the Canine interface or references a type library that contains a definition for the Canine interface.

 Sub StartBarking(Can As Canine)  Can.Bark End Sub 

This subroutine is totally unaware of the type of object its Can parameter is referencing. But the compiler will guarantee that any type of object that is passed to the StartBarking subroutine will support the Canine interface, or else the compiler will produce an error. What the compiler is doing in this case is officially known as type checking. For example, if classes Poodle and Wolf both support the Canine interface, but class Iguana does not, the compiler will permit both Poodle and Wolf objects to be passed to the StartBarking subroutine, but it will raise an error when an Iguana object is passed. The following code fragment illustrates this:

 ' Declare object variables. Dim myPoodle As Poodle Dim myWolf As Wolf Dim myIguana As Iguana ' Construct new instances. Set myPoodle = New Poodle Set myWolf = New Wolf Set myIguana = New Iguana StartBarking myPoodle ' OK StartBarking myWolf  ' OK StartBarking myIguana ' Failed: Type Mismatch 

I call this smart polymorphism because the Visual Basic compiler is smart enough to notice at compile time whether a given class implements an interface expected by an object variable. Because the compiler can type check the object variables in the code fragment above, you can safely assume that the overhead for invoking a property or method will be miniscule. This low overhead results from the early binding technique that is used by the compiler because of how the object variables were declared.

Oblivious Polymorphism

Oblivious polymorphism occurs when you declare an object variable as type Object. Consequently, the object variable can reference any type of object without concern for the interfaces the object supports, so long as subsequent property or method invocations are supported by the default interface of that object. Because an interface contract is not defined, the compiler does no type checking at compile time for object variables of type Object. Therefore, the compiler is oblivious to whether the object variable is referencing an object that supports the properties or methods it is invoking. As a result, everything checks out OK by the compiler. Here's an example:

 Sub StartBarking(Can As Object) Can.Bark End Sub  ' Declare object variables. Dim myPoodle As Poodle Dim myWolf As Wolf Dim myIguana As Iguana ' Construct new instances. Set myPoodle = New Poodle Set myWolf = New Wolf Set myIguana = New Iguana StartBarking myPoodle ' OK StartBarking myWolf ' OK StartBarking myIguana ' OK 

At first glance, this type of polymorphism might seem more ideal than smart polymorphism, but it comes at a high price. Because the compiler does no type checking at compile time, it is highly feasible that errors will occur at run time. In the code extract above, all calls to the StartBarking method could potentially fail at run time, including the calls where the Poodle and Wolf objects are used, even if those objects inherit an interface that supports the Bark method. At run time, object variables of type Object can bind only to properties or methods of the interface they requested from the object they are referencing. In the code extract above, all interfaces requested by the Can parameter of the StartBarking subroutine are the default interfaces for each type of object. To further clarify my point, the code extract could be written as follows:

 ' Declare object variables. Dim myPoodle As Object Dim myWolf As Object Dim myIguana As Object ' Construct new instances. Set myPoodle = New Poodle Set myWolf = New Wolf Set myIguana = New Iguana myPoodle.Bark myWolf.Bark myIguana.Bark 

Each object variable of type Object will be assigned a reference to an object of type Poodle, Wolf, or Iguana. Each type of object returns its default interface. Because the compiler was oblivious at compile time, the system must compensate at run time by using significantly more overhead than the compiler because the system must locate and validate the Bark method to invoke it per the object variable's instructions. This is a late binding technique because the program must go through this validation process every time a method is invoked, regardless of whether it was called previously. For example, using the previous code extract, if you were to invoke the Bark method on the myPoodle object variable twice in a row, the overhead would be the same both times. This overhead can be a significant price to pay, especially if the object being invoked is not within the same thread or process it's being invoked from.

In summary, Visual Basic supports polymorphism using either an early binding technique (smart polymorphism) or a late binding technique (oblivious polymorphism). I highly recommend you use smart polymorphism whenever possible.



Microsoft Visual Basic Design Patterns
Microsoft Visual Basic Design Patterns (Microsoft Professional Series)
ISBN: B00006L567
EAN: N/A
Year: 2000
Pages: 148

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net