Saturday, September 8, 2012

A Design Pattern for Console Apps


You could continue to put all processing logic in the Main routine of your console application, but eventually you would get tired of always typing the same basic code. All of your programs would start to look pretty similar to Passthrough and Replace. It would be nice if you could inherit some functionality and simply change the parts that you need to change. You can achieve such an effect with a design pattern known as the Template Method. In it, you create a skeleton algorithm in a base class and then allow child classes to fill in the details. Expressed in pseudocode, the skeleton algorithm might look like the following pseudocode snippet:
If the arguments to the program are valid then
    Do necessary pre-processing
    For every line in the input
        Do necessary input processing
    Do necessary post-processing
Otherwise
    Show the user a friendly usage message
An example of subclass responsibility is to define what pre-processing, post-processing, and input processing actually mean to that particular subclass. You could put logic like this in the static Main method, effectively making it the skeleton algorithm, but this presents two problems. First, console apps compile to executables. .NET executables can be used as class libraries, but Visual Studio .NET makes that difficult to do. Thus, it's hard to allow subclasses to fill in the details because it's difficult to create subclasses.
The second problem is that Main is a static method. Static methods don't belong to instances of a class; they belong to the class itself. This means that all the steps of the algorithm (pre-process, post-process, and so on) would have to be implemented as static methods as well, preventing them from being overridden. Only instance methods can be overridden and, as such, the methods representing the steps of the skeleton algorithm need to be instance methods of a class.
The best way to get around these limitations is to separate each console application into two classes. The first class is just the normal console application class with the static Main entry point (call it the "chassis"). Instead of containing processing logic, however, the Main routine creates an instance of the second class (call it the "engine") and delegates processing to it. For the Passthrough example, the chassis would look like the code in Figure 3. Notice how the engine class has a similar entry point, Main, but that it is an instance method rather than a static method. The Main method of the engine class will become your skeleton algorithm, allowing you to employ the Template Method design pattern. ConsoleEngineBase is the name of the class that will hold the implementation of the skeleton algorithm and all engines will inherit from it, supplying their own versions of the algorithm's steps along the way. The complete code for ConsoleEngineBase is given in Figure 4.
using System;

namespace Engines
{
    public abstract class ConsoleEngineBase
    {
        private string[] m_args;
        protected bool ReadInput = true;
        protected System.IO.TextReader In = null;
        protected System.IO.TextWriter Out = null;
        protected System.IO.TextWriter Error = null;

        public ConsoleEngineBase()
        {
            //by default, read from/write to standard streams
            this.In = System.Console.In;
            this.Out = System.Console.Out;
            this.Error = System.Console.Error;
        }

        public void Main(string[] args)
        {
            this.m_args = args;    

            if(this.ValidateArguments())
            {
                this.PreProcess();
                if(this.ReadInput)
                {
                    string currentLine = this.In.ReadLine();
                    while(currentLine != null)
                    {
                        this.ProcessLine(currentLine);
                        currentLine = this.In.ReadLine();
                    }
                }
                this.PostProcess();
            }
            else
                this.Error.Write("Usage: " + this.Usage());
        }

        public void Main(string[] args, 
            System.IO.TextReader In, 
            System.IO.TextWriter Out, 
            System.IO.TextWriter Error)
        {
            //this version of Main allows alternate streams
            this.In = In;
            this.Out = Out;
            this.Error = Error;
            this.Main(args);
        }

        protected virtual bool ValidateArguments()
        {
            //override this to add custom argument checking
            return true;
        }

        protected virtual string Usage()
        {
            //override this to add custom usage statement 
            return "";
        }

        protected virtual void PreProcess()
        {
            //override this to add custom logic that 
            //executes just before standard in is processed
            return;
        }

        protected virtual void PostProcess()
        {
            //override this to add custom logic that 
            //executes just after standard in is processed
            return;
        }

        protected virtual void ProcessLine(string line)
        {
            //override this to add custom processing 
            //on each line of input
            return;
        }

        protected string[] Arguments
        {
            get {return this.m_args;}
        }
    }
}
using System;
using Engines;

namespace ConsoleApps
{
    class PassthroughChassis
    {
        [STAThread]
        static void Main(string[] args)
        {
            Engines.ConsoleEngineBase engine = new PassthroughEngine();
            engine.Main(args);
        }
    }
}
Since PassthroughEngine inherits from ConsoleEngineBase, it can be implemented very simply. The complete PassthroughEngine class is shown here:
public class PassthroughEngine : ConsoleEngineBase
{
    public override void ProcessLine(string line)
    {
        this.Out.WriteLine(line);
    }
}
Passthrough doesn't need to bother with any special pre- or post-processing or checking of arguments, so it doesn't override these steps of the algorithm. It needs to do one thing—echo every line of input to output—and it does this by providing its own implementation of ProcessLine. Figure 5 summarizes the interactions between the classes in this model.
Figure 5 Class Relationships 
The benefit of employing the Template Method design pattern is obvious: inherited classes only need to change the parts of the algorithm that they require, resulting in less overall coding. But what are the consequences of breaking every console application into chassis and engine classes? There are at least two negative consequences: first, you end up with twice as many classes as before, and second, you end up with a bunch of really simple, similar chassis classes. I think these drawbacks, however, are more than outweighed by the benefits.
The first, most important benefit is that engine classes are now free to inherit from one another. PassthroughEngine can be implemented with one line of code. A second, more subtle benefit is that the engine can be chosen at run time—they are pluggable. Since all engine classes derive from ConsoleEngineBase, the chassis doesn't really care which one is used. In fact, you could generalize PassthroughChassis to be configurable at run time. Figure 6 shows the code for a chassis class that requires an assembly name and class name as command-line arguments. It uses this information to create an instance of a particular engine class. It then calls its Main method, effectively turning over processing to it. For Passthrough, the command would look like this:
C:\> GenericChassis "Engines" "Engines.PassthroughEngine"
In this case, the PassthroughEngine class is a member of the Engines namespace and resides in an assembly called Engines.dll (note that the DLL extension must be omitted from the argument in this example).
using System;
using Engines;

namespace ConsoleApps
{
    class GenericChassis
    {
        [STAThread]
        static void Main(string[] args)
        {
            if(args.Length < 2)
            {
                Console.Error.WriteLine("Two arguments required:"); 
                Console.Error.WriteLine("assembly and class name.");
                return;
            }

            Engines.ConsoleEngineBase engine = null;
            string assemName = args[0];
            string className = args[1];
            string[] newargs = new string[args.Length - 2];

            for(int i = 2; i < args.Length; i++)
            {
                newargs[i - 2] = args[i];
            }

            try
            {
                Runtime.Remoting.ObjectHandle engineHandle = 
                    Activator.CreateInstance(assemName, className);
                engine = (ConsoleEngineBase)engineHandle.Unwrap();
            }
            catch(Exception e)
            {
                Console.Error.WriteLine(e.Message);
                return;
            }
            engine.Main(newargs);
        }
    }
}
This approach—separating the chassis from the engine—is usually called the Strategy design pattern (though I prefer the car metaphor). It allows an algorithm to change without having to create an entire subclass of its associated class. What's so bad about subclassing just to change one method? Having lots of classes that differ only in their implementation of a single algorithm can be confusing and hard to maintain. Families of algorithms are generally thought to be more intuitive than families of classes. In Figure 5, ConcreteEngine1 and ConcreteEngine2 are members of this family. Physically, they are implemented as classes; logically, they exist only to help the chassis implement its Main method. (This might have been more intuitively accomplished with delegates, though it would have complicated the use of the Template Method pattern.)
You may have noticed that there isn't anything console-specific about these engine classes. They simply know how to read and write System.IO.Streams. This is another benefit of this approach—the same logic that you use from the console can be used from (for example) a network server class. Just as you can swap out the engine, you can swap out the chassis, albeit with a little more work.
Even engines that are more complex than Passthrough are similarly straightforward. Think back to the Replace example. Replace is similar to Passthrough except that it performs a regular expressions-based replacement on each line of input. The complete listing for ReplaceEngine is shown in Figure 7. Notice how the PreProcess method sets up a single instance of the System.Text.RegularExpressions.Regex class and the ProcessLine method uses it on each line of input. I'll build further on these later in the article. For now, however, let's look at the other side of console apps: how to control them from within a .NET Framework-based app.
using System;
using System.Text.RegularExpressions;

namespace Engines
{
    public class ReplaceEngine : ConsoleEngineBase
    {
        private Regex replacer = null;

        protected override void PreProcess()
        {
            this.replacer = new Regex(this.Arguments[0]);
        }

        protected override void ProcessLine(string line)
        {
            this.Out.WriteLine(
                this.replacer.Replace(line, this.Arguments[1]));
        }

        protected override string Usage()
        {
            return "Replace \"findexp\" \"replaceexp\"";
        }

        protected override bool ValidateArguments()
        {
            if(this.Arguments.Length == 2)
                return true;
            return false;
        }
    }
}

No comments:

Post a Comment