George Lucas was quoted as saying that he originally envisioned Star Wars as a series of nine movies. The first movie (self-titled) came out in 1976, followed by The Empire Strikes Back and Return of the Jedi. Almost thirty years since the first movie was released, prequels 1, 2, and 3 came out, which makes the first three movies episodes 4, 5, and 6—I guess. It's all sort of confusing.
This is the third and final installment of the Prime Programming Proficiency series—and you didn't have to wait 30 years for it. The first installment discussed heuristics and how you can use lines of code as a rudimentary starting place for establishing efficient software development. The subject led to many more questions than answers, but questions are good beginnings. The second installment introduced the extensibility object model and macros, setting the stage for implementing a built-in, lines-of-code counter using macros and VB.NET.
The Problem and Solution Summarized
The problem is trying to track the number of projects, files, and lines of code in any given solution implemented with VS.NET. The solution—stated in Part 2 as a list of imperatives—is to invoke the utility in VS.NET and implement it as a macro that other teams can share. Finally, the utility displays running sub-totals and final totals (which include project and file names, project and file counts, and lines-of-code counts for source code files) when it has examined all files. This is manageable.
Introducing the Visitor Pattern
What are design patterns? I think of them as solution blueprints. Someone else had a problem, solved it successfully (and hopefully simplified some things), factored out domain-specific information, and then published his or her ideas. A design is an instance of one or more patterns, and software is an instance of a design. It might seem strange that software preceded design, which preceded patterns, but this is what happens with most if not all things. Innovation is followed by formalization, which is a precursor to mass production, automation, and finally, obsolescence.
The Visitor pattern is called a behavior pattern because it describes a solution for how something behaves. The Visitor pattern is useful when one encounters a problem where an action needs to be performed on a group of things. The action might be as simple as modifying a property or as complex as creating new objects. Think of an after-hours security guard making rounds at a government facility. Every hour, he has to ensure that all the doors are locked, a complicated and time-consuming operation considering that the facility has several floors, hundreds of doors, and multiple buildings. Now, suppose the security guard also has to check alarm systems, safes, parking facilities, and walk Mrs. Johnson to her car. The guard is still stopping at—or visiting—each thing, but what he does when he gets there is different depending on what he's visiting. For instance, Mrs. Johnson doesn't want to be jiggled and the door can't be walked to the parking lot. If the guard's duties were a programming solution, the Visitor pattern would separate the visiting behavior—or the making of rounds—from the action that occurs during the course of the visit.
Tip: Good designs are often based on metaphorical physical processes with distinct, well-known boundaries.
In this article's problem domain, a solution, project, and file are all elements known to Visual Studio .NET. While we want to visit each, we want to do something different with each, based on its type. Because we implement our solution as a macro, we have certain constraints that must be implemented too, like a macro entry point. Let's start there.
Defining the Macro Entry Point
Design patterns do not solve all of our problems. We might use many patterns in an application, using some patterns more than once. Other things do not require patterns. For example, a macro entry point simply is a subroutine that VS.NET requires; it has little to do with the Visitor pattern. The starting point for our solution is shown in Listing 1.
Listing 1: The macro starting point.
Option Explicit Off Imports EnvDTE Imports System.Diagnostics Imports System.Windows Imports System.Windows.Forms Imports System Imports System.IO Imports System.Text.RegularExpressions Imports System.text Public Module MyUtilities Public Sub DumpProject() Dim i As SolutionIterator = New SolutionIterator i.Iterate() End Sub End Module
Generally, I start working with objects and move to the user interface, which means I actually wrote the code in Listing 1 last. As you can see, the DumpProject method is very straightforward: It creates something called SolutionIterator and calls Iterate. Simple code like Listing 1 should be familiar to you; it is about the same level of complexity as the Main subroutine in Windows Forms and Console applications.
Listing 2 shows the SolutionIterator. This class and its Iterate method are also quite simple. Iterator creates an instance of a class called Visitor and a SolutionElement that is provided with the current solution.
Listing 2: The SolutionIterator class.
Imports EnvDTE Imports System.Diagnostics Public Class SolutionIterator Public Sub Iterate() Dim visitor As Visitor = New Visitor Dim solutionElement As SolutionElement = _ New SolutionElement(DTE.Solution.Projects) solutionElement.Accept(visitor) Output.WriteLine("Total Line Count: " & visitor.LineCount) Output.WriteLine("Total Files: " & visitor.ItemCount) Output.WriteLine("Total Projects: " & visitor.ProjectCount) End Sub End Class
After you create the solutionElement, you invoke an Accept method. When Accept returns, you should have your totals.
The pieces you need to create, as Listing 2 suggests, are the Visitor, SolutionElement class, and the Output class. The Output class is a wrapper for the VS.NET IDE's Output window, and Visitor and SolutionElement are dependent on the Visitor pattern. Now, you are getting into the meat of the solution.