Writing applications often feels like déjà vu all over again, to quote a certain New York Yankees catcher. This is especially true when you write elements such as collection code, event systems, adapters between interfaces, and encapsulated algorithmscode you write exactly the same way most of the time.
These common code elements often consist of reusable logic you can abstract into design patterns. Applying a few simple concepts (outlined in this article) can help you identify such code segments and turn them into reusable code. Specifically, I'll describe how to identify code for reuse, as well as the concept of reuse with design patterns and VB templates. Following a simplified methodology of requirements, design, and implementation enables you to create a reusable template for the Iterator pattern. This approach employs class-templates based on object-oriented patterns that replace the tired, wizard-generated collection class code.
An Iterator pattern is "a way to access the elements of an aggregate object sequentially without exposing its underlying representation" (see Chapter 5, "Behavioral Patterns," Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides). The idea behind the pattern is to remove the responsibility and logic of cycling through a list by putting the list into a separate object (see Figure 1 for a class diagram representing an Iterator pattern).
Two classes comprise the pattern: List and ListIterator. The List class is responsible for maintaining the list of data. Its internal implementation can vary from a buffer pulling data from a database on demand to an array or a collection of objects. The ListIterator class is responsible for implementing the traversing logic and keeping track of the current item. It also does any filtering on the data in the List class. Note that you implement the List class like a collection class: You add data to it and retrieve data from it. You implement ListIterator like a recordset, with methods such as MovePrevious and MoveNext. These two classes are standard interfaces for handling data: The collection class (List class) stores the data, while the recordset (ListIterator class) traverses it.
You must decide what type of environment you'll use your template in when you determine your template's requirements. In the era of Internet development, you want to create a template for objects you can use in both Visual Basic and VBScript. This means the template needs to operate in both service-based and object-oriented environments (see the sidebar, "A Different State of Architecture"). You target VBScript and the service-based environment because they're the lowest denominators.
Your target language is VBScript, so you must consider its particular properties in your requirements: VBScript can access only the default interface of a class. This means you can't use interfaces to implement polymorphism; instead, you must rely on declaring things "As Object" to use polymorphism (see Resources for more information on interfaces and polymorphism).
Create Your Design
You now know the template's requirements; next, you want to create your design. Recall that you have two classes, List and ListIterator. The List class needs a method to return an instance of ListIterator because the Iterator pattern dictates that the List class is responsible for creating its ListIterator. You implement the rest of the List class much as you would a custom collection class (similar to the type generated by the Class wizard).
Next, design the classes' interfaces (see Listing 1 for the List class's interface definition). The method names are fairly self-documenting, so anybody familiar with collections should understand them. The only differences between your List class and the code the Class wizard might generate are in the Add, Exists, and CreateIterator methods.
The Add method accepts an object and an optional key. In contrast, the code generated by the Class wizard creates a list of property values. The Iterator approach keeps the List class generic and reusable. The template's requirements dictate that you need to be able to operate in a service-based environment. In this context, a separate manager-type class encapsulates the logic for retrieving the data and populating the list, a loose example of the Factory pattern in action (see Resources for more information on Factory classes). Accepting a generic object parameter also enables the list's content to be heterogeneous, which might be useful in the future.
The List class's Exists method is a useful tool for verifying the existence of an item in the class's underlying data. The CreateIterator method creates an instance of a ListIterator and returns it. Different kinds of ListIterators might implement different kinds of filtering, so you can consider the CreateIterator function an implementation of the Factory pattern. The methods and properties of the ListIterator class should be familiar to you if you've used a recordset before (see Listing 2).
You implement the design by creating the List and ListIterator classes. Note: Don't call them clsList or ClistIterator because you'll use them as templates, and you want their names to be meaningful.
ListIterator is responsible for traversing the List and for tracking the current index (see Listing 2 for ListIterator's implementation). It uses two module-level variables to do this: mobjList and mlngIteratorIndex. The code for the Move methods is simplethe methods all validate the index pointer, and either increment or decrement it. Using error constants lets you raise standard documented errors in your code. Also, declaring these constants as a Public Enum makes it easier to test for errors:
Public Enum enumIteratorErrors
ERR_ITERATOR_NOTLOADED = 50001
ERR_ITERATOR_BOF = 50002
ERR_ITERATOR_EOF = 50003
Of course, making the Enum part of the class will cause problems if more than one ListIterator exists in the same project.
The compiler raises an error similar to "Ambiguous name detected: enumIteratorErrors" if you have multiple declarations of the same Enum in your project (that is, multiple ListIterator classes). You can solve this problem in a couple ways. You can make the Enum private, but this isn't useful because you can't use it outside your project. Or, you can limit yourself to one Iterator class per project, using it as a generic List class. However, this isn't useful if you plan to apply custom filtering through the CreateIterator method and want to take advantage of the Factory pattern.
You solve this problem by creating a generic project template, then discovering and applying patterns to the way you develop applications. You can probably think of several tasks that could benefit from having reusable code. For example, consider the time it takes to add a standard module such as a modMain.bas with a Main() subroutine in it or a module name such as modAPI.bas that contains all your API declarations.
You can make this part automatic by using a project template. First, create a new ActiveX DLL, then name the project MyDllTemplate. Next, add a class, name it CEnums, and set its instancing property to 2-PublicNotCreatable. Remove the Enum declaration from your class template and insert it in the CEnums class. Now add whatever modules and classes you usually add to a new project. Finally, save your project in the \VB98\Templates\Projects\ directory. Now you'll see a project called MyDllTemplate when you select the New Projects menu option. Using this method puts you well on your way to creating a development framework based on your coding habits and standards.
You can reuse the List and ListIterator templates once you create them. Patterns are about reuse, and templates are the mechanism for reusing them. Preparing the templates for reuse requires documenting them, so start documenting the classes by inserting standard function headers into them. The header's format is up to you, but keep it clean and to the point because someone else using your template might need to make sense of it. You might also want to include some TODO: tags to indicate places where you need to modify your code. Programmers using your templates can perform a search on "TODO:" in the class module to figure out quickly where they must insert code manually.
Next, save your classes to this installation directory [VS Path] where you installed Visual Studio or VB:
You should now see two new class types, List and ListIterator, whenever you select ADD - Class Module from the menu.
You can use this technique to reuse your standard class designs and project setups in the future. The goal: to save time by not implementing and re-implementing all the details each time you start a new project. A little elbow grease can often help you come up with a generic design suitable for nearly any situation.
Create List and ListIterator Templates
You're almost done. You've implemented polymorphism in the code you've written by declaring object references "As Object." You can use this method to make the code VBScript friendly, but it's a less than ideal technique.
A better way to create generic List and ListIterator templates is to define their interfaces in separate classes called abstract (or interface) classes. Do this by creating two classes named IList and IListIterator, then inserting the function and property declarations from your templates in them. The resulting classes resemble the code in Listing 1, but include only the function declarations with no code inside them. You can now compile these interface classes, as well as the Enum declarations, into a separate type library (DLL). Use them by referencing them from your project template. Using interfaces also lets you implement a better Factory pattern in the manager-type class and a better CreateIterator() method in the List class (see Resources for more information on interface-based programming).
Many object-oriented concepts don't translate directlyor even easilyinto VB5 or VB6 code because they use inheritance. Until VB.NET, that is. With inheritance, the process described in this article could change dramatically. You would create a series of base classes instead of templates, inheriting from the base classes. The resulting collection of base classes is called an object-oriented framework. An example of a familiar object-oriented framework is the Microsoft Foundation Class Library (MFC). In MFC programming, classes you create inherit from the MFC classesin effect, passing down their functionality to your classes. You can always override or extend functionality if you aren't happy with the way it's implemented.
VB.NET will also feature both visual and class inheritance. These new features will give your developer's toolkit a powerful addition: reuse through inheritance. With this process, you can look forward to creating a framework with visual and class inheritance by writing a series of base forms and classes, then inheriting from them.
Marc D'Aoust is vice president of research and development for OSTnet OpenSource Technologies, a Montreal-based software development firm. You can reach him at firstname.lastname@example.org.