Polymorphic Filters

Interfaces are the COM way (and the Visual Basic way) of providing polymorphism. Let’s define an interface for polymorphic filter classes. Filters are the class equivalent of MS-DOS filters such as SORT or MORE. The role of a filter is to convert data from one form to another by applying a set of rules.

The filter we’ll define consists of two parts. It must iterate through lines of text, reading them from a source and writing them to a target. This code works the same regardless of what the filter does. The filter must also apply the transformation to each line. This code is different for every kind of filter. It’s a familiar problem. One part of the code is generic; one part is specific. You want to reuse the generic part but provide different implementations of the specific part. So you write an interface for the generic part and you write classes that implement the interface for the specific part. The generic code uses the objects with specific implementations without knowing or caring what those objects do or how they do it. All that matters is that the objects match the interface.

This technique turns object-oriented programming on its head, as shown in Figure 3-5 on page 152. The primary purpose of the CDrive class discussed earlier was encapsulation. All implementation details of a particular operation are hidden within the class, and many different users call the same class in different ways to get different results. With polymorphism, you implement multiple versions of a class with the same interface. One user calls the standard interface of any of the class implementations to get different results.

Figure 3-5. Encapsulation and polymorphism.

Another way to look at interfaces is that they provide one more level of abstraction. Interfaces encapsulate classes in the same way that classes encapsulate members and methods. Just as a class hides the internal state of its objects, an interface hides the implementation of its classes. If that leaves you more confused than before, well, let’s get to the examples.

I use filter classes to implement wizard programs. For example, the Bug Wizard program uses the CBugFilter class to transform assertion and profile statements. The Global Wizard program uses several different classes to transform global classes into standard modules and vice versa. All these classes implement the IFilter interface.

In the filter problem, the generic part is the FilterTextFile or the FilterText procedure (in FILTER.BAS). FilterTextFile applies a filter to a text file specified by name in the Source and Target properties of the filter. FilterText works similarly except that the actual text to be transformed (rather than the name of a file containing the text) is passed in the Source and Target properties. Both of these procedures work the same no matter what the filter does. The specific part is the filter class (CBugFilter, one of the global filter classes, or your own favorite filter class). The filter class implements the generic IFilter interface with class-specific code that analyzes each line of text and modifies it according to its own rules.

In languages with inheritance, the generic part of the algorithm (the FilterTextFile or FilterText procedure) might be provided as a non-virtual method in a base class that also contains virtual methods for the specific part. Filter classes would inherit the FilterTextFile or FilterText method from the base class and use it without change, but would implement the virtual methods. You use a different strategy in Visual Basic, mixing functional and object-oriented techniques. The generic FilterTextFile and FilterText procedures go in a standard module. They take IFilter parameters to receive the object that provides the application-specific part of the algorithm. There are many other ways to use polymorphism, and we’ll see some of them in later chapters.