4.4 Polymorphism

only for RuBoard

4.4 Polymorphism

You may also want to change how a behavior in the base class works by redefining it in your derived class. Authorizing, billing, and crediting are very different processes, depending on what type of payment you are talking about.

While the Payment class can provide minimal functionality that is common across all of the various payment types, a derived class still needs to redefine or most likely extend the base class implementation. This process is called overriding .

You might also want to provide another way to initialize the object. Currently, the account number is passed directly to New . In the real world, the constructor would probably take a customer ID and a payment ID of some kind, and the card number would be retrieved from a database (where it was stored encrypted). You could add an additional constructor to do this. Providing more than one way to do the same thing with an object is called overloading . Overloaded functions have the same name but different arguments to distinguish them from one another.

The power of inheritance is not apparent until polymorphism is brought into the picture. In fact, the two concepts go hand in hand, so much so that discussing one without the other is very difficult.

The word polymorphism means "many forms." Polymorphism allows a derived class to be passed to a method that expects a base class. It allows methods from a base class to be redefined in a derived class. It also allows methods to be declared with the same name but different arguments.

This chapter and Chapter 5 discuss many forms of polymorphism and how to use each form effectively. While inheritance and polymorphism are powerful tools for writing robust, reusable object hierarchies, they give the uninitiated an unprecedented opportunity to write some really bad code. Thus, rather than just discuss the syntax, a few fundamental concepts will be covered as well. Covering these concepts will ensure that these technologies be used in a way that is ultimately beneficial.

only for RuBoard
only for RuBoard

4.5 Overloading

Look at Payment.Bill (in Example 4-1) for minute. In its current state, it does not take any parameters. Whatever amount was authorized is the amount that will be billed. But the online shopping world is strange . It is possible that when this payment is billed, not all of the items that were originally purchased will be in stock. Should the order be canceled ? Of course not. You should notify the customer, bill for what you have, and ship the order. The problem is that the customer can't be billed for anything that hasn't shipped. And as it stands now, there is no way to bill a partially available order by using the Payment class.

You need an additional billing method that allows an amount to be specified. It is possible to define two or more methods that have the same name , but a different number of parameters. Then the functionality can be implemented for each case. Example 4-5 shows two versions of Bill : one that accepts an amount and one that does not. This process is called overloading.

Example 4-5. Overloading a method
 Imports System
   
Public MustInherit Class Payment
  
  'Other class methods are here 
  
  Public Function Bill( ) As Boolean
    'Bill authorized amount
  End Function
   
  Public Function Bill(ByVal amount As Double) As Boolean
    If amount > Me.amount Then
      amount = Me.amount
    End If
    'Bill specified amount here
  End Function
  
End Class 

If a method is overloaded in a derived class, then it needs to be explicitly expressed by using the Overloads keyword:

 Public Class CreditCard : Inherits Payment
   
  'Other methods here
   
  'Overloading method defined in Payment requires keyword  Public Overloads Function Bill(ByVal amount As Double) As Boolean  'Bill for amount specified
  End Function
   
End Class 

The compiler knows which call is made based on the parameters that are passed, as the following code fragment shows:

 Dim visa As New CreditCard("4111111111111111")
visa.Bill( )      'Calls Payment.Bill
visa.Bill(3.33)   'Calls CreditCard.Bill 

Overloading is based on the name of the method and the arguments it takes. A method cannot be overloaded by return type, so overloading properties is out of the question.

4.5.1 Overloaded Constructors

It is not uncommon for a class designer to overload the constructor of a class and provide several different ways to initialize an object. In the .NET class library, for instance, the String class provides several different constructors.

A string can be initialized with a literal:

 Dim someString As String = "Literal" 

or an array of characters :

 Dim otherString As New String(New Char( ) {"a"c, "r"c, "r"c, "a"c, "y"c}) 

The c following the individual elements of the array distinguishes each element as a type of Char versus a 1-character String .

The string can be initialized with a character and an integer that represents how many times the character should be repeated:

 'A string that contains 10 question marks
Dim thatString As String = New String("?"c, 10) 

These constructors are just a few that are defined for a string. This was accomplished by overloading the New method to handle the various types of arguments. Notice that no two constructors have the same signature. You can similarly define constructors with different signatures for your own classes to provide more than one way to initialize your object. However, you should know how overloading a constructor differs from overloading an ordinary method.

4.5.1.1 The default constructor

Constructors are not inherited, so, technically, they cannot be overloaded from one class to the next . The compiler does, however, add a call to the default constructor (a constructor that has no arguments) in the parent class from the derived class, effectively chaining the classes together. This move is sensible because the derived class might rely on the parent class being in a known state. If a default constructor isn't explicitly defined, the compiler puts an empty one into the class during compilation. Example 4-6 demonstrates the process.

Example 4-6. Constructor behavior
 Imports System
   
Public MustInherit Class Payment
   
  Public Sub New( )
    Console.WriteLine("Payment.New")
  End Sub
   
  Public Sub New(ByVal account As String)
    Console.WriteLine("Payment.New: {0}", acctNumber)
  End Sub
   
End Class
   
Public Class CreditCard : Inherits Payment
   
  Public Sub New( )
    Console.WriteLine("CreditCard.New")
  End Sub
   
  Public Sub New(ByVal account As String)
    Console.WriteLine("CreditCard.New: {0}", account)
  End Sub
   
End Class
   
Friend Class Test
  Public Shared Sub Main( )
    Dim visa As New CreditCard( )
    Dim amex As New CreditCard("3111111111111")
  End Sub
End Class 

Both the child class and the parent class define constructors with the same signatures. Regardless of which constructor is called in the subclass, though, the default constructor in the base class is always called first, even if the parameterized constructor is called:

 Payment.New
CreditCard.New
Payment.New
CreditCard.New: 3111111111111 

Remember that the default constructor is called first, not the New method with the matching signature. In this case, the base class constructor with the matching function signature should be called instead of the default constructor. You can call it by using the MyBase keyword to call the appropriate base class constructor directly from the derived constructor as follows :

 Public Class CreditCard : Inherits Payment
   
    Public Sub New( )
        Console.WriteLine("CreditCard.New")
    End Sub
    
    Public Sub New (ByVal acctNumber As String)
        MyBase.New(acctNumber) 
        Console.WriteLine("CreditCard.New: {0}", acctNumber)
    End Sub
    
End Class 

Now the following line of code:

 Dim amex As New CreditCard("3111111111111") 

Would produce this expected result:

 Payment.New: 311111111111111
CreditCard.New: 311111111111111 

The default constructor of the base class is not called at all.

When calling a constructor in the parent class using MyBase , make sure it is the first thing that happens in the derived class' constructor. For example:

 Public Sub New(ByVal x As Integer)
    MyBase.New(x)
    'Do additional initialization here
End Sub 

With other overloaded methods, the call to the base class method using MyBase can be at the beginning, the middle, or the end of a method call, depending on when the functionality provided by the parent implementation is needed. Or it may not be needed at all.

only for RuBoard