Polymorphism


Polymorphism is often considered to be directly tied to inheritance (which is discussed next). In reality, however, it’s largely independent. Polymorphism means that you can have two classes with different implementations or code, but with a common set of methods, properties, or events. You can then write a program that operates upon that interface and doesn’t care about which type of object it operates at runtime.

Method Signatures

To properly understand polymorphism, you need to explore the concept of a method signature, sometimes also called a prototype. All methods have a signature, which is defined by the method’s name and the datatypes of its parameters. You might have code such as this:

  Public Function CalculateValue() As Integer End Sub 

In this example, the signature is as follows:

 f()

If you add a parameter to the method, the signature will change. For example, you could change the method to accept a Double:

  Public Function CalculateValue(ByVal value As Double) As Integer 

Then, the signature of the method is as follows:

 f(Double)

Polymorphism merely says that you should be able to write client code that calls methods on an object, and as long as the object provides your methods with the method signatures you expect, it doesn’t matter from which class the object was created. Let’s look at some examples of polymorphism within Visual Basic.

Implementing Polymorphism

You can use several techniques to achieve polymorphic behavior:

  • Late binding

  • Multiple interfaces

  • Reflection

  • Inheritance

Late binding actually enables you to implement “pure” polymorphism, although at the cost of performance and ease of programming. Through multiple interfaces and inheritance, you can also achieve polymorphism with much better performance and ease of programming. Reflection enables you to use either late binding or multiple interfaces, but against objects created in a very dynamic way, even going so far as to dynamically load a DLL into your application at runtime so that you can use its classes.

You’ll walk through each of these options to see how they are implemented and to explore their pros and cons.

Polymorphism through Late Binding

Typically, when you interact with objects in Visual Basic, you are interacting with them through strongly typed variables. For example, in Form1 you interacted with the Encapsulation object with the following code:

 Private Sub btnEncapsulation_Click(ByVal sender As System.Object, _   ByVal e As System.EventArgs) Handles btnEncapsulation.Click   Dim obj As New Encapsulation   MsgBox(obj.DistanceTo(10, 10))    End Sub

The obj variable is declared using a specific type (Encapsulation) - meaning that it is strongly typed or early bound.

You can also interact with objects that are late bound. Late binding means that your object variable has no specific datatype, but rather is of type Object. To use late binding, you need to use the Option Strict Off directive at the top of your code file (or in the project’s properties). This tells the Visual Basic compiler that you want to use late binding, so it will allow you to do this type of polymorphism. Add the following to the top of the Form1 code:

  Option Strict Off 

With Option Strict turned off, Visual Basic treats the Object datatype in a special way, enabling you to attempt arbitrary method calls against the object even though the Object datatype doesn’t implement those methods. For example, you could change the code in Form1 to be late bound as follows:

 Private Sub btnEncapsulation_Click(ByVal sender As System.Object, _   ByVal e As System.EventArgs) Handles btnEncapsulation.Click    Dim obj As Object = New Encapsulation    MsgBox(obj.DistanceTo(10, 10)) End Sub

When this code is run, you get the same result as you did before, even though the Object datatype has no DistanceTo method as part of its interface. The late-binding mechanism, behind the scenes, dynamically determines the real type of your object and invokes the appropriate method.

When you work with objects through late binding, neither the Visual Basic IDE nor the compiler can tell whether you are calling a valid method. Here, there is no way for the compiler to know that the object referenced by your obj variable actually has a DistanceTo() method. It just assumes that you know what you’re talking about and compiles the code.

At runtime, when the code is actually invoked, it attempts to dynamically call the DistanceTo() method. If that is a valid method, your code will work; if it is not, you’ll get an error.

Obviously, there is a level of danger when using late binding, as a simple typo can introduce errors that can only be discovered when the application is actually run. However, it also offers a lot of flexibility, as code that makes use of late binding can talk to any object from any class as long as those objects implement the methods you require.

There is a substantial performance penalty for using late binding. The existence of each method is discovered dynamically at runtime, and that discovery takes time. Moreover, the mechanism used to invoke a method through late binding is not nearly as efficient as the mechanism used to call a method that is known at compile time.

To make this more obvious, you can change the code in Form1 by adding a generic routine that displays the distance:

 Private Sub btnEncapsulation_Click(ByVal sender As System.Object, _   ByVal e As System.EventArgs) Handles btnEncapsulation.Click   Dim obj As New Encapsulation   ShowDistance(obj) End Sub Private Sub ShowDistance(ByVal obj As Object)    MsgBox(obj.DistanceTo(10, 10)) End Sub 

Notice that the new ShowDistance routine accepts a parameter using the generic Object datatype - so you can pass it literally any value - String, Integer, or one of your own custom objects. It will throw an exception at runtime, however, unless the object you pass into the routine has a DistanceTo() method that matches the required method signature.

You know that your Encapsulation object has a method matching that signature, so your code works fine. However, let’s add another simple class to demonstrate polymorphism. Add a new class to the project and name it Poly.vb:

 Public Class Poly   Public Function DistanceTo(ByVal x As Single, ByVal y As Single) As Single     Return x + y   End Function End Class

This class is about as simple as you can get. It exposes a DistanceTo() method as part of its interface and provides a very basic implementation of that interface.

You can use this new class in place of the Encapsulation class without changing the ShowDistance() method by using polymorphism. Return to the code in Form1 and make the following change:

 Private Sub btnEncapsulation_Click(ByVal sender As System.Object, _   ByVal e As System.EventArgs) Handles btnEncapsulation.Click   Dim obj As New Poly   ShowDistance(obj) End Sub

Even though you changed the class of object you’re passing to ShowDistance() to one with a different overall interface and different implementation, the method called within ShowDistance() remains consistent, so your code will run.

Polymorphism with Multiple Interfaces

Late binding is flexible and easy, but it isn’t ideal because it defeats the IDE and compiler type checking that enables you to fix bugs due to typos during the development process. It also has a negative impact on performance.

Another way to implement polymorphism is to use multiple interfaces. This approach avoids late binding, meaning that the IDE and compiler can check your code as you enter and compile it. Moreover, because the compiler has access to all the information about each method you call, your code will run much faster.

Remove the Option Strict directive from the code in Form1. This will cause some syntax errors to be highlighted in the code, but don’t worry - you’ll fix those soon enough.

Visual Basic not only supports polymorphism through late binding, but also implements a stricter form of polymorphism through its support of multiple interfaces. (Earlier you learned about multiple interfaces, including the use of the Implements keyword and how to define interfaces.)

With late binding, you’ve learned how to treat all objects as equals by making them all appear using the Object datatype. With multiple interfaces, you can treat all objects as equals by making them all implement a common datatype or interface.

This approach has the benefit that it is strongly typed, meaning the IDE and compiler can help you find errors due to typos because the names and datatypes of all methods and parameters are known at design time. It is also fast in terms of performance; because the compiler knows about the methods, it can use optimized mechanisms for calling them, especially compared to the dynamic mechanisms used in late binding.

Let’s return to the project and implement polymorphism with multiple interfaces. First, add a module to the project using the Project image from book Add Module menu option and name it Interfaces.vb. Replace the Module code block with an Interface declaration:

  Public Interface IShared   Function CalculateDistance(ByVal x As Single, ByVal y As Single) As Single End Interface 

Now you can make both the Encapsulation and Poly classes implement this interface. First, in the Encapsulation class add the following code:

 Public Class Encapsulation   Implements IShared   Private mX As Single   Private mY As Single   Public Function DistanceTo(ByVal x As Single, ByVal y As Single) _       As Single Implements IShared.CalculateDistance   Return CSng(Sqrt((x - mX) ^ 2 + (y - mY) ^ 2)) End Function

Here you’re implementing the IShared interface, and because the CalculateDistance method’s signature matches that of your existing DistanceTo method, you’re simply indicating that it should act as the implementation for CalculateDistance().

You can make a similar change in the Poly class:

 Public Class Poly   Implements IShared   Public Function DistanceTo(ByVal x As Single, ByVal y As Single) As Single _       Implements IShared.CalculateDistance         Return x + y   End Function End Class

Now this class also implements the IShared interface, and you’re ready to see polymorphism implemented in your code. Bring up the code window for Form1 and change your ShowDistance() method as follows:

  Private Sub ShowDistance(ByVal obj As IShared)   MsgBox(obj.CalculateDistance(10, 10)) End Sub 

Note that this eliminates the compiler error you saw after removing the Option Strict directive from Form1.

Instead of accepting the parameter using the generic Object datatype, you are now accepting an IShared parameter - a strong datatype known by both the IDE and the compiler. Within the code itself, you are now calling the CalculateDistance() method as defined by that interface.

This routine can now accept any object that implements IShared, regardless of what class that object was created from, or what other interfaces that object may implement. All you care about here is that the object implements IShared.

Polymorphism through Reflection

You’ve seen how to use late binding to invoke a method on any arbitrary object as long as that object has a method matching the method signature you’re trying to call. You’ve also walked through the use of multiple interfaces, which enables you to achieve polymorphism through a faster, early-bound technique. The challenge with these techniques is that late binding can be slow and hard to debug, and multiple interfaces can be somewhat rigid and inflexible.

Enter reflection. Reflection is a technology built into the .NET Framework that enables you to write code that interrogates an assembly to dynamically determine the classes and datatypes it contains. Using reflection, you can load the assembly into your process, create instances of those classes, and invoke their methods.

When you use late binding, Visual Basic makes use of the System.Reflection namespace behind the scenes on your behalf. You can choose to manually use reflection as well. This gives you even more flexibility in how you interact with objects.

For example, suppose that the class you want to call is located in some other assembly on disk - an assembly you didn’t specifically reference from within your project when you compiled it. How can you dynamically find, load, and invoke such an assembly? Reflection enables you to do this, assuming that the assembly is polymorphic. In other words, it has either an interface you expect or a set of methods you can invoke via late binding.

To see how reflection works with late binding, let’s create a new class in a separate assembly (project) and use it from within your existing application. Choose File image from book Add image from book New Project to add a new class library project to your solution. Name it Objects. It begins with a single class module that you can use as a starting point. Change the code in that class to the following:

  Public Class External   Public Function DistanceTo(ByVal x As Single, ByVal y As Single) As Single     Return x * y   End Function End Class 

Now compile the assembly by choosing Build image from book Build Objects. Next, bring up the code window for Form1. Add an Imports statement at the top, and add back the Option Strict Off statement:

  Option Strict Off Imports System.Reflection 

Remember that because you’re using late binding, Form1 also must use Option Strict Off. Without this, late binding is not available.

Then add a button with the following code:

 Private Sub Button1_Click(ByVal sender As System.Object, _        ByVal e As System.EventArgs) Handles button1.Click     Dim obj As Object     Dim dll As Assembly          dll = Assembly.LoadFrom("..\..\Objects\bin\Objects.dll")          obj = dll.CreateInstance("Objects.External")     MsgBox(obj.DistanceTo(10, 10))   End Sub

There’s a lot going on here, so let’s walk through it. First, notice that you’re reverting to late binding; your obj variable is declared as type Object. You’ll look at using reflection and multiple interfaces in a moment, but for now you’ll use late binding.

Next, you’ve declared a dll variable as type Reflection.Assembly. This variable will contain a reference to the Objects assembly that you’ll be dynamically loading through your code. Note that you are not adding a reference to this assembly via Project image from book Add References. You’ll dynamically get access to the assembly at runtime.

You then load the external assembly dynamically by using the Assembly.LoadFrom method:

 dll = Assembly.LoadFrom("..\..\Objects\bin\Objects.dll")

This causes the reflection library to load your assembly from a file on disk at the location you specify. Once the assembly is loaded into your process, you can use the myDll variable to interact with it, including interrogating it to get a list of the classes it contains or to create instances of those classes.

Important 

You can also use the [Assembly].Load method, which will scan the directory where your application’s .exe file is located (and the global assembly cache) for any EXE or DLL containing the Objects assembly. When it finds the assembly, it loads it into memory, making it available for your use.

You can then use the CreateInstance method on the assembly itself to create objects based on any class in that assembly. In your case, you’re creating an object based on the External class:

 obj = dll.CreateInstance("Objects.External")

Now you have an actual object to work with, so you can use late binding to invoke its DistanceTo() method. At this point, your code is really no different from that in the earlier late-binding example, except that the assembly and object were created dynamically at runtime, rather than being referenced directly by your project.

At this point, you should be able to run the application and have it dynamically invoke the assembly at runtime.

Polymorphism via Reflection and Multiple Interfaces

You can also use both reflection and multiple interfaces together. You’ve seen how multiple interfaces enable you to have objects from different classes implement the same interface and thus be treated identically. You’ve also seen how reflection enables you to load an assembly and class dynamically at runtime.

You can combine these concepts by using an interface shared in common between your main application and your external assembly, and using reflection to load that external assembly dynamically at runtime.

First, create the interface that will be shared across both application and assembly. To do this, add a new Class Library project to your solution named Interfaces. Once it is created, drag and drop the Interfaces.vb module from your original application into the new project (hold down the Shift key as you move it). This makes the IShared interface part of that project and no longer part of your base application.

Of course, your base application still uses IShared, so you’ll want to reference the Interfaces project from your application to gain access to the interface. Do this by right-clicking your OOExample project in the Solution Explorer window and selecting Add Reference. Then add the reference, as shown in Figure 4-15.

image from book
Figure 4-15

Because the IShared interface is now part of a separate assembly, add an Imports statement to Form1, Encapsulation, and Poly so that they are able to locate the IShared interface:

  Imports Interfaces 

Be sure to add this to the top of all three code modules.

You also need to have the Objects project reference Interfaces, so right-click Objects in Solution Explorer and choose Add Reference there as well. Add the reference to Interfaces and click OK. At this point, both the original application and the external assembly have access to the IShared interface. You can now enhance the code in Objects by changing the External class:

 Imports Interfaces Public Class External   Implements IShared   Public Function DistanceTo(ByVal x As Single, ByVal y As Single) _       As Single Implements IShared.CalculateDistance     Return x * y   End Function End Class

With both the main application and external assembly using the same datatype, you are now ready to implement the polymorphic behavior using reflection.

Remove the Option Strict Off code from Form1. In fact, you might also want to explicitly set Option Script On. This prohibits the use of late binding, thus ensuring that you must use a different technique for polymorphism.

Bring up the code window for Form1 and change the code behind the button to take advantage of the IShared interface:

 Private Sub btnReflection_Click(ByVal sender As System.Object, _   ByVal e As System.EventArgs) Handles Button1.Click   Dim obj As IShared   Dim dll As Assembly   dll = Assembly.LoadFrom("..\..\Objects\bin\Objects.dll")   obj = CType(dll.CreateInstance("Objects.External"), IShared)   ShowDistance(obj) End Sub

All you’ve done here is change the code so that you can pass your dynamically created object to the ShowDistance() method, which you know requires a parameter of type IShared. Because your class implements the same IShared interface (from Interfaces) used by the main application, this will work perfectly. Rebuild and run the solution to see this in action.

This technique is very nice, as the code in ShowDistance() is strongly typed, providing all the performance and coding benefits, but both the DLL and the object itself are loaded dynamically, providing a great deal of flexibility to your application.

Polymorphism with Inheritance

Inheritance, discussed earlier in this chapter, can also be used to enable polymorphism. The idea here is very similar to that of multiple interfaces, as a subclass can always be treated as though it were the datatype of the parent class.

Important 

Many people consider the concepts of inheritance and polymorphism to be tightly intertwined. As you’ve seen, however, it is perfectly possible to use polymorphism without inheritance.

At the moment, both your Encapsulation and Poly classes are implementing a common interface named IShared. You can use polymorphism to interact with objects of either class via that common interface. The same is true if these are child classes based on the same base class through inheritance. To see how this works, in the OOExample project, add a new class named Parent and insert the following code:

 Public MustInherit Class Parent   Public MustOverride Function DistanceTo(ByVal x As Single, _       ByVal y As Single) As Single End Class

As described earlier, this is an abstract base class, a class with no implementation of its own.

The purpose of an abstract base class is to provide a common base from which other classes can be derived. To implement polymorphism using inheritance, you do not need to use an abstract base class. Any base class that provides overridable methods (using either the MustOverride or Overridable keywords) will work fine, as all its subclasses are guaranteed to have that same set of methods as part of their interface and yet the subclasses can provide custom implementation for those methods.

In this example, you’re simply defining the DistanceTo() method as being a method that must be overridden and implemented by any subclass of Parent. Now you can bring up the Encapsulation class and change it to be a subclass of Parent:

 Public Class Encapsulation   Inherits Parent   Implements IShared

You don’t need to stop implementing the IShared interface just because you’re inheriting from Parent; inheritance and multiple interfaces coexist nicely. You do, however, have to override the DistanceTo() method from the Parent class.

The Encapsulation class already has a DistanceTo() method with the proper method signature, so you can simply add the Overrides keyword to indicate that this method will override the declaration in the Parent class:

  Public Overrides Function DistanceTo( _   ByVal x As Single, _ByVal y As Single) _   As Single Implements IShared.CalculateDistance 

At this point, the Encapsulation class not only implements the common IShared interface and its own native interface, but also can be treated as though it were of type Parent, as it is a subclass of Parent. You can do the same thing to the Poly class:

 Public Class Poly   Inherits Parent   Implements IShared   Public Overrides Function DistanceTo( _       ByVal x As Single, ByVal y As Single) _       As Single Implements IShared.CalculateDistance     Return x + y   End Function End Class

Finally, you can see how polymorphism works by altering the code in Form1 to take advantage of the fact that both classes can be treated as though they were of type Parent. First, you can change the ShowDistance() method to accept its parameter as type Parent and to call the DistanceTo() method:

  Private Sub ShowDistance(ByVal obj As Parent)   MsgBox(obj.DistanceTo(10, 10)) End Sub 

Then, you can add a new button to create an object of either type Encapsulation or Poly and pass it as a parameter to the method:

 Private Sub btnInheritance_Click(ByVal sender As System.Object, _   ByVal e As System.EventArgs) Handles btnInheritance.Click   ShowDistance(New Poly)   ShowDistance(New Encapsulation) End Sub

Polymorphism Summary

Polymorphism is a very important concept in object-oriented design and programming, and Visual Basic provides you with ample techniques through which it can be implemented.

The following table summarizes the different techniques and their pros and cons, and provides some high-level guidelines about when to use each:

Open table as spreadsheet

Technique

Pros

Cons

Guidelines

Late binding

Flexible, “pure” polymorphism

Slow, hard to debug, no IntelliSense

Use to call arbitrary methods on literally any object, regardless of datatype or interfaces

Useful when you can’t control the interfaces that will be implemented by the authors of your classes

Multiple interfaces

Fast, easy to debug, full IntelliSense

Not totally dynamic or flexible, requires class author to implement formal interface

Use when you are creating code that interacts with clearly defined methods that can be grouped together into a formal interface

Useful when you control the interfaces that will be implemented by the classes used by your application

Reflection and late binding

Flexible, “pure” polymorphism, dynamically loads arbitrary assemblies from disk

Slow, hard to debug, no IntelliSense

Use to call arbitrary methods on objects4 when you don’t know at design time which assemblies you will be using

Reflection and multiple interfaces

Fast, easy to debug, full IntelliSense, dynamically loads arbitrary assemblies from disk

Not totally dynamic or flexible, requires class author to implement formal interface

Use when you are creating code that interacts with clearly defined methods that can be grouped together into a formal interface, but when you don’t know at design time which assemblies you will be using

Inheritance

Fast, easy to debug, full IntelliSense, inherits behaviors from base class

Not totally dynamic or flexible, requires class author to inherit from common base class

Use when you are creating objects that have an is-a relationship, i.e., when you have subclasses that are naturally of the same datatype as a base class.

Polymorphism through inheritance should occur because inheritance makes sense, not because you are attempting to merely achieve polymorphism.




Professional VB 2005 with. NET 3. 0
Professional VB 2005 with .NET 3.0 (Programmer to Programmer)
ISBN: 0470124709
EAN: 2147483647
Year: 2004
Pages: 267

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