Chapter 6. Inheritance and Interfaces

As you begin to work more with classes, you soon come across programming cases where you have classes that are similar to others you are already using in this program or another one. It seems a shame to just copy all that code over again and have a lot of objects that are separate but very alike.

In languages like Java and VB.NET, you can derive new classes from existing classes and change only those methods that differ in the new class, with the unchanged parent methods called automatically. VB6 does not support this level of inheritance, but it does provide interfaces and implementations that allow you to produce related classes with only a small amount of effort.

Interfaces

In VB, you can create a class containing only definitions of the methods and no actual code. This is called an interface definition. Then you can create other classes that implement that interface, and they all can be treated as if they were instances of the parent interface, even though they implement the methods differently.

For example, you could create an interface called Command that has the following methods.

Public Sub Execute()

End Sub
'------
Public Sub init(nm As String)

End Sub

Then you could create a number of Command objects, such as ExitCommand, that implement the Command interface. To do this, you create a class called ExitCommand, and insert this line.

Implements Command

Then, from the left drop-down you select the Command interface, and from the right drop-down you create instances of the Execute and init methods. You can now fill in these methods with whatever code is appropriate.

Private Sub Command_Execute()
'do something
End Sub
'------
Private Sub Command_init(nm As String)
'initialize something
End Sub

The advantage of this approach is that the ExitCommand class is now also of the type Command, and all of the classes that implement the Command interface can be treated as instances of the Command class. To see how this can be helpful, let's consider a program for simulating investment growth.

An Investment Simulator

Our investment simulation program will present us with a mixture of stocks and bonds, and we can look at their growth during any time interval. We will assume that the bonds are all tax-free municipal bonds and that all the stocks have positive growth rates.

The program starts with a list of seven stocks and bonds and an investment nest egg of $10,000 to use. You can invest in any combination of stocks and bonds at any rate until all your money is invested. The initial program state is shown in Figure 6-1.

An investment simulator at the start

Figure 6-1. An investment simulator at the start

You select investments by highlighting them, selecting a purchase amount, and clicking on the Buy button.

Once you have selected some stocks, you can enter any future date and compute the total stock value and the total taxable income. For simplicity, we assume that the stock income is all taxable and that the bond income is all nontaxable. A typical investment result is shown in Figure 6-2.

A typical investment result

Figure 6-2. A typical investment result

The taxable income display is shown in Figure 6-3.

Taxable income results

Figure 6-3. Taxable income results

Writing the Simulator

The most important class in the simulator represents a stock. This class has an init method that sets the name and type (stock or bond) and an invest method that determines the date and how much was invested.

Private stockName As String     'name
Private isMuniBond As Boolean   'true of muni bond
Private investment As Single    'amount invested
Private invDate As Date         'date of investment
Private rate As Single          'growth rate
'------
Public Sub init(nm As String, muniBond As Boolean)
 stockName = nm          'save the name
 isMuniBond = muniBond   'and whether a bond
 If isMuniBond Then
   rate = 0.05           'low fixed rate for bonds
 Else
   rate = Rnd / 10       'random rate for stocks
 End If
End Sub
'-----
Public Sub invest(amt As Single)
 invDate = CVDate(Date$) 'remember date
 investment = amt        'and amount invested
End Sub

Then, when we ask for the amount of the investment or the taxable amount earned, we compute them based on the days elapsed since the investment.

Public Function getName() As String
 getName = stockName     'return the stock name
End Function
'------
Public Function getValue(toDate As Date) As Single
 Dim diff, value As Single
 'compute the value of the investment
 diff = DateDiff("d", invDate, toDate)
 value = (diff / 365) * rate * investment + investment
 getValue = value        'and return it
End Function
'------
Public Function getTaxable(toDate As Date) As Single
 If isMuniBond Then
   getTaxable = 0        'no taxable income
 Else
   'return the taxable income
   getTaxable = getValue(toDate) - investment
 End If
End Function

Indicators for Using an Interface

There are two places in the preceding code where we have to ask what kind of investment this is. One is when we decide on the rate and the other is when we decide on the taxable return. Whenever you see decisions like this inside classes, you should treat them as a yellow caution flag indicating that there might be a better way. Why should a class have to make such decisions? Would it be better if each class represented only one type of investment? If we did create a Stock and a Bond class, the program would become more complicated because our display of list data assumes the data are all of type stock.

Private Sub Taxable_Click()
 Dim i As Integer, dt As Date
 Dim stk as Stock
 lsOwn.Clear
 dt = CVDate(txDate.Text)
 For i = 1 To stocksOwned.Count
   Set stk = stocksOwned(i)
   lsOwn.AddItem stk.getName & vbTab & _
       Format$(stk.getTaxable(dt), "####.00")
 Next i
End Sub

Instead, we'll create a new class called Equity and derive the Stock and Bond classes from it. Here is our empty Equity interface.

Public Sub init(nm As String)
End Sub
'------
Public Sub invest(amt As Single)
End Sub
'------
Public Function getName() As String
End Function
'------
Public Function getValue(toDate As Date) As Single
End Function
'------
Public Function getTaxable(toDate As Date) As Single
End Function
'------
Public Function isBond() As Boolean
End Function

Now, our Stock class just becomes the following.

Implements Equity
Private stockName As String     'stock name
Private investment As Single    'amount invested
Private invDate As Date         'investment date
Private rate As Single          'rate of return
'------
Private Function Equity_getName() As String
  Equity_getName = stockName    'return the name
End Function
'------
Private Function Equity_getTaxable(toDate As Date) As Single
 'compute the taxable include
 Equity_getTaxable = Equity_getValue(toDate) - investment
End Function
'------
Private Function Equity_getValue(toDate As Date) As Single
 Dim diff, value As Single
 'compute the total value of the investment to date
 diff = DateDiff("d", invDate, toDate)
 value = (diff / 365) * rate * investment + investment
 Equity_getValue = value
End Function
'------
Private Sub Equity_init(nm As String)
  stockName = nm    'initialize the name
  rate = Rnd / 10   'and a rate
End Sub
'------
Private Sub Equity_invest(amt As Single)
  invDate = CVDate(Date$)   'set the date
  investment = amt          'and the amount
End Sub
'------
Private Function Equity_isBond() As Boolean
  Equity_isBond = False  'is not a bond
End Function

The Bond class is pretty similar, except for the getTaxable, init, and isBond methods.

Implements Equity
Private stockName As String
Private investment As Single
Private invDate As Date
Private rate As Single
'------
Private Function Equity_getName() As String
 Equity_getName = stockName
End Function
'------
Private Function Equity_getTaxable(toDate As Date) As Single
 Equity_getTaxable = 0
End Function
'------
Private Function Equity_getValue(toDate As Date) As Single
 Dim diff, value As Single

 diff = DateDiff("d", invDate, toDate)
 value = (diff / 365) * rate * investment + investment
 Equity_getValue = value
End Function
'------
Private Sub Equity_init(nm As String)
  stockName = nm
  rate = 0.05
End Sub
'------
Private Sub Equity_invest(amt As Single)
  invDate = CVDate(Date$)
  investment = amt
End Sub
'------
Private Function Equity_isBond() As Boolean
  Equity_isBond = True
End Function

However, by making both Stocks and Bonds implement the Equity interface, we can treat them all as Equities, since they have the same methods, rather than having to decide which kind is which.

Private Sub Taxable_Click()
Dim i As Integer, dt As Date
Dim stk as Equity
'Show the list of taxable incomes
 lsOwn.Clear
 dt = CVDate(txDate.Text)
 For i = 1 To stocksOwned.Count
   Set stk = stocksOwned(i)
   lsOwn.AddItem stk.getName & vbTab & _
        Format$(stk.getTaxable(dt), "####.00")
 Next i
End Sub

Reusing Common Methods

A quick glance at the preceding code shows that the Stock and Bond classes have some duplicated code. One way to prevent this from happening is to put some methods in the base class and then call them from the derived classes. While the initial idea was to make the interface module just a series of empty methods, VB6 does not require this, and they can indeed have code in them.

For example, we could rewrite the basic Equity class to contain another method that actually computes the interest and is called by the derived classes.

'Class Equity with calcValue added
Public Sub init(nm As String)
End Sub
'------
Public Sub invest(amt As Single)
End Sub
'------
Public Function getName() As String
End Function
'------
Public Function getValue(toDate As Date) As Single
End Function
'------
Public Function getTaxable(toDate As Date) As Single
End Function
'------
Public Function isBond() As Boolean
End Function
'------
Public Function calcValue(invDate As Date, toDate As Date, _
       rate As Single, investment As Single)
 Dim diff, value As Single

 diff = DateDiff("d", invDate, toDate)
 value = (diff / 365) * rate * investment + investment
 calcValue = value
End Function

Now, since we don't have real inheritance in VB6, we can't call this from the derived classes directly, but we can insert an instance of the Equity class inside the Stock and Bond classes and call its calcValue method. This simplifies the Stock class to the following.

Implements Equity
Private stockName As String     'stock name
Private investment As Single    'amount invested
Private invDate As Date         'date of investment
Private rate As Single          'rate of return
Private eq As New Equity        'instance of base Equity class
'------
Private Function Equity_getName() As String
  Equity_getName = stockName
End Function
'------
Private Function Equity_getTaxable(toDate As Date) As Single
  Equity_getTaxable = Equity_getValue(toDate) - investment
End Function
'------
Private Function Equity_getValue(toDate As Date) As Single
'compute using method in base Equity class
  Equity_getValue = eq.calcValue(invDate, toDate, rate, _
                       investment)
End Function
'------
Private Sub Equity_init(nm As String)
  stockName = nm
  rate = Rnd / 10
End Sub
'------
Private Sub Equity_invest(amt As Single)
  invDate = CVDate(Date$)
  investment = amt
End Sub
'------
Private Function Equity_isBond() As Boolean
  Equity_isBond = False
End Function

Now, since the calcValue method is part of the interface, you have to include an empty method by that name in the Stock and Bond classes as well so the classes can compile without error.

Private Function Equity_calcValue(invDate As Date, _
       toDate As Date, rate As Single, _
       investment As Single) As Variant
       'never used in child classes
End Function

You could avoid this by creating an ancillary class that contains the computation method and creating an instance of it in the Stock and Bond classes, but this does lead to more clutter of extra classes.

Hidden Interfaces

Another way of accomplishing the same thing in this particular case is to give Stock and Bond the same public interfaces without using an Equity interface at all. Since all the operations in this simple program take place through a Collection, we can obtain a collection item and call its public methods without ever knowing which type of equity it actually is. For example, the following code will work for a collection Stocks containing a mixture of Stock and Bond objects.

For i = 1 To stocks.Count
  sname = stocks(i).getName
 lsStocks.AddItem sname
Next i

You should recognize, however, that this special case only occurs because we never need to get the objects back out as any particular type. In the cases we develop in the chapters that follow, this will seldom be the case.

Summary

In this chapter, we've shown how to construct an interface and a set of classes that implement it. We can then refer to all the derived classes as if they were an instance of the interface class and simplify our code considerably.

Programs on the CD-ROM

InheritanceBasic Investment simulator
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset