Using an interface class


Now let’s look at the generic FilterTextFile procedure (in FILTER.BAS). Notice that it takes an IFilter parameter:

Sub FilterTextFile(filter As IFilter)

BugAssert filter.Source <> sEmpty
‘ Target can be another file or replacement of current file
Dim sTarget As String, fReplace As Boolean
sTarget = filter.Target
If sTarget = sEmpty Or sTarget = filter.Source Then
sTarget = MUtility.GetTempFile(“FLT”, “.”)
fReplace = True
End If

‘ Open input file
On Error GoTo FilterTextError1
Dim nIn As Integer, nOut As Integer
nIn = FreeFile
Open filter.Source For Input Access Read Lock Write As #nIn

‘ Open target output file
On Error GoTo FilterTextError2
nOut = FreeFile
Open sTarget For Output Access Write Lock Read Write As #nOut

‘ Filter each line
On Error GoTo FilterTextError3
Dim sLine As String, iLine As Long, eca As EChunkAction
Do Until EOF(nIn)
Line Input #nIn, sLine
iLine = iLine + 1
eca = filter.Translate(sLine, iLine)
Select Case eca
Case ecaAbort
GoTo FilterTextError3 ‘ Stop processing
Case ecaTranslate
Print #nOut, sLine ‘ Write modified line to output
Case ecaSkip
‘ Ignore
Case Else
BugAssert True ‘ Should never happen
End Select
Loop

‘ Close files
On Error GoTo FilterTextError1
Close nIn
Close nOut
If fReplace Then
‘ Destroy old file and replace it with new one
Kill filter.Source
On Error Resume Next ‘ No more errors allowed
Name sTarget As filter.Source
‘ If this fails, you’re in trouble
BugAssert Err = 0
End If
Exit Sub

FilterTextError3:
Close nOut
FilterTextError2:
Close nIn
FilterTextError1:
MErrors.ErrRaise Err
End Sub

If you study the code carefully, you can see that it does not depend in any way on what the filter actually does. FilterTextFile just loops through each line of Source, calling the Translate method on each line and writing the result to Target. It doesn’t matter what file names Source or Target contain or what Translate does to the text.


If you have read the previous edition of this book, you might remember that the old version of FilterTextFile took an Object parameter rather than an IFilter parameter. The description of the old FilterTextFile was largely a diatribe on what real object-oriented languages did and how that compared to Visual Basic’s pitiful version of polymorphism. Well, that flame is gone. Visual Basic’s version of polymorphism didn’t come out the way I expected. But, the functionality is there, and…well, judge for yourself.


Now that FilterTextFile takes an IFilter, you can’t just pass a form or a ListBox or some other random object. Any object you pass must have a class that implements IFilter. Passing an inappropriate object causes a polite error at compile time rather than a rude one at run time. More importantly, Visual Basic can bind the IFilter calls at compile time, making polymorphic classes almost as fast as non-polymorphic ones. That’s a big turnaround from version 4 where polymorphic algorithms were often as much as 10 times slower than comparable non-polymorphic algorithms.


I’ve shown FilterTextFile here because it’s more interesting, but I’ll be using FilterText in some of the examples. FilterText does a similar operation, but it assumes that Source and Target contain a text string with each line of text separated by a carriage return/line feed combination. The code just grabs lines of text from Source, filters them, and writes the result to Target. The Bug Wizard uses FilterTextFile and the Global Wizard uses FilterText for reasons that need not concern us. There’s nothing sacred about Source and Target. You could write filter procedures that assume Source and Target are URLs or database record identifiers.


NOTE The error trap in FilterTextFile doesn’t have anything to do with polymorphic classes, but it does illustrate a stair-stepped error trap. Often, when something goes wrong while you’re building something with a series of Basic statements, you need to unbuild
it in the opposite order, undoing only the parts you have finished. A carefully designed series of error traps can take you back to
the initial state.