Click here to Skip to main content
15,861,172 members
Articles / Programming Languages / C#
Article

Illustrated GOF Design Patterns in C# Part IV: Behavioral I

Rate me:
Please Sign up or sign in to vote.
4.67/5 (12 votes)
31 Mar 2003CPOL9 min read 161.4K   449   167   9
Part IV of a series of articles illustrating GOF Design Patterns in C#

Abstract

Design Patterns, Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides [also known as the Gang of Four (GOF)] has been a de facto reference for any Object-Oriented software developer. This article is Part IV of a series of articles illustrating the GOF Design Patterns in C#. We will discuss the Chain of Responsibility, Command, and Interpreter patterns. The next few articles will conclude the C# illustrations of the GOF design patterns. It is assumed the reader is familiar with basic C# syntax and conventions, and not necessarily details of the .NET Framework.

Background

In Design Patterns, each pattern is described with its name (and other well-known names); the motivation behind the pattern; its applicability; the structure of the pattern; class/object participants; participant collaborations; pattern consequences; implementation; sample code; known uses; and related patterns. This article will only give a brief overview of the pattern, and an illustration of the pattern in C#.

A design pattern is not code, per se, but a "plan of attack" for solving a common software development problem. The GOF had distilled the design patterns in their book into three main subject areas: Creational, Structural, and Behavioral. This article deals with the Behavioral design patterns, or how objects are act together. The first three articles in this series dealt with the Creational and Structural patterns. This article begins the illustration of the Behavioral patterns as described by the Gang of Four.

This article is meant to illustrate the design patterns as a supplement to their material. It is recommended that you are familiar with the various terms and object diagram methods used to describe the design patterns as used by the GOF. If you're not familiar with the diagrams, they should be somewhat self-explanatory once viewed. The most important terms to get your head around are abstract and concrete. The former is a description and not an implementation, while the latter is the actual implementation. In C#, this means an abstract class is an interface, and the concrete class implements that interface.

Behavioral Patterns

To quote the GOF, "Behavioral patterns are concerned with algorithms and the assignment of responsibilities between objects. Behavioral patterns describe not just patterns of objects or classes but also the patterns of communication between them. These patterns characterize complex control flow that's difficult to follow at run-time. They shift your focus away from flow of control to let you concentrate just on the way objects are interconnected."

Chain of Responsibility

There are times when designing software that one comes across the need to pass requests between objects, and multiple objects at that. The Chain of Responsibility pattern allows more than one object to handle a request. Request receivers are "chained" until an object handles it. You move from a specific request handler to the generic, passing the request from object to object until it is finally handled.

A good example is when you throw an exception from a method. You first get a chance to handle it in a local try / catch block. If it isn't handled locally, the exception passes to the caller, and so on up the chain until it is handled. At last, if there are no application exception handlers, the runtime finally handles it.

The structure of the Chain of Responsibility looks like this:

Chain of Responsibility structure

For our illustration (chain.cs), we have the following scenario: We're developing a bug tracking system for a software company, and depending on the type of bug (UI, functionality, et al.) it must get handled, routed and so on, but at the least it must be submitted to a bug database. We begin with defining a BugType structure for enumerating the types of bugs, and a BugHandler which will be the base class for handling:

C#
public enum   BugType   {   Any, Feature, UI   }

public class   BugHandler
{
   private BugHandler   m_oSuccessor;   //   in order to chain

   public   BugHandler(BugHandler o)   {   m_oSuccessor = o;   }

   virtual public void   HandleBug(BugType t)
   {
      if (m_oSuccessor != null)
      {
         Console.WriteLine("...{0} passing to successor {1}",
            this.GetType().ToString(), m_oSuccessor.GetType().ToString());

         m_oSuccessor.HandleBug(t);
      }
      else   throw new Exception("Bug not handled!");
   }
}

Our default handling routine HandleBug just passes along the request, throwing an error if no successor exists. Our derived classes will call base.HandleBug() if they do not handle the specific bug type:

C#
public class   FeatureBugHandler : BugHandler
{
   public   FeatureBugHandler(BugHandler o)   :   base(o)
   {
      //   ...
   }

   override public void   HandleBug(BugType t)
   {
      if (BugType.Feature == t)
         Console.WriteLine("-->  FeatureBugHandler:  {0}", t.ToString());
      else   base.HandleBug(t);   //   pass onto successor
   }
}

The client can then "build" a chain of handlers (in any order, mind you). In the sample code, we create two chains, and fire off HandleBug for each BugType. The output looks like this:

Chain 1 UI:
-->  UIBugHandler:  UI
Chain 2 UI:
...Concrete.FeatureBugHandler passing to successor Concrete.UIBugHandler
-->  UIBugHandler:  UI
Chain 1 Feature:
...Concrete.UIBugHandler passing to successor Concrete.FeatureBugHandler
-->  FeatureBugHandler:  Feature
Chain 2 Feature:
-->  FeatureBugHandler:  Feature
Chain 1 Any:
...Concrete.UIBugHandler passing to successor Concrete.FeatureBugHandler
...Concrete.FeatureBugHandler passing to successor Concrete.GenericBugHandler
-->  GenericBugHandler:  Any
Chain 2 Any:
...Concrete.FeatureBugHandler passing to successor Concrete.UIBugHandler
...Concrete.UIBugHandler passing to successor Concrete.GenericBugHandler
-->  GenericBugHandler:  Any

One could just as easily create and chain together drawing handlers for various UI elements, or application-specific events. If one wished, you could use a .NET event that "kicks off" the chain.

Command

There are times when developing an application framework that you need to pass requests between objects without knowing anything about operation requested or the receiver of the request. By encapsulating the request as an object itself, one can "parameterize clients with different requests, queue or log requests, and support undoable operations." (GOF) This is the Command behavioral design pattern. You can separate the requesting object from the object that "knows" how to fulfill it.

The Command pattern structure:

Command structure

The GOF recommend using the Command pattern when you want to:

  • parameterize objects by an action to perform (much like SqlCommands allowing different types of command text and execute methods)
  • specify, queue, and execute requests at different times. Your Command object may live independently of a request
  • support undo. State can be stored on the Command object, and an "undo" operation performs a "reverse" of the execute. For unlimited undo/redo, a list of Commands could be maintained, and one could traverse it backwards or forwards calling "undo" or "execute" appropriately
  • build systems from "primitive or atomic operations" such as transactions

To illustrate using this pattern (command.cs,) suppose we are modelling flying an airplane, specifically, tuning the communication radios. We want to be able to set a frequency, but later be able to go back to a previous one. We'll define an interface for the radio command:

C#
public interface   RadioCommand
{
   void   Execute(double f);
   void   Undo();
}

In the future, we may be performing more than just tuning a radio frequency with a radio command. Our concrete implementation of this command will perform some action on our radio class, namely setting the desired frequency, or "undoing" the change:

C#
public class   ChangeFreqCommand : RadioCommand
{
   private double   m_fOld;   //   our old frequency
   private Radio   m_oRadio;   //   the radio we're concerned with

   public   ChangeFreqCommand(Radio o)
   {
      m_oRadio = o;
      m_fOld = 0.0;
   }

   public void   Execute(double f)
   {
      //   store the old frequency, then set to desired
      m_fOld = m_oRadio.Frequency;
      m_oRadio.Frequency = f;
   }

   public void   Undo()
   {
      m_oRadio.Frequency = m_fOld;
   }
}

Our client will create and use ChangeFreqCommand objects to play with a radio:

C#
ArrayList   cmds = new ArrayList();
Radio   r = new Radio("Garmin COM", 121.5);   //   start on guard channel
ChangeFreqCommand   c = new ChangeFreqCommand(r);

c.Execute(125.25);   //   get ATIS
cmds.Add(c);
c = new ChangeFreqCommand(r);
c.Execute(121.9);   //   tune to ground
cmds.Add(c);

// ...

To undo, we just iterate through our ArrayList backwards and call Undo():

C#
cmds.Reverse();

foreach(ChangeFreqCommand x in cmds)   x.Undo();

It's worthwhile to note that we are not checking for bogus Radio or Command states, so the undo/redo may not always work as planned (see my remarks in the Conclusion of this article.)

Interpreter

I've worked on projects where there was a need to tokenize input, and store it in an application's "grammar." The Interperter pattern's intent is "Given a language, define a represention for its grammar along with an interpreter that uses the representation to interpret sentences in the language." (GOF) This is quite useful if you are assembling commands for batch processing that need a more "human" way of inputting controlling statements to your batch processes, or just plain searching a string. You are building or using a syntax "tree," based on the grammar. The Interpreter structure can be represented as follows:

Interpreter structure

This is probably one of the more "fuzzy" patterns out there, since you have to define a grammar, get it into "computer" form, then interpret it. Probably the best way to illustrate this pattern is to consider a simple RPN calculator.

A Reverse Polish Notation [RPN] calculator operates by placing the elements of an operation before the operation itself. For example:

5 4 +
would yield 9, or
3 4 *
which would yield 12.

For our sample (interpreter.cs,) our grammar is simple: We take integers and operators as input. We regard an the addition operator (+) or an integer to be a terminal expression (it is composed of nothing else,) and a full line of input passed to the parser to be our only non-terminal expression. We won't support nested operations (e.g., in parenthesis.) Our context will be a stack (ArrayList) of inputted integers or the result from an addition. The expression classes would look like this:

C#
public interface   AbstractExpression
{
   void   Interpret(ArrayList c);
}

public class   NonTerminalExpression : AbstractExpression
{
   public ArrayList   expressions;   //   our contained expressions

   public   NonTerminalExpression()
   {
      expressions = new ArrayList();
   }

   public void   Interpret(ArrayList c)
   {
      //   walk our contained expressions (possibly other non-terminals)
      foreach (AbstractExpression e in expressions)
         e.Interpret(c);
   }
}

public class   IntegerExpression : AbstractExpression
{
   public int   literal;   //   storage for our literal

   public   IntegerExpression(int n)
   {
      literal = n;
   }

   public void   Interpret(ArrayList c)
   {
      c.Add(literal);
      Console.WriteLine("...pushed {0}", literal);
   }
}

public class   AdditionTerminalExpression : AbstractExpression
{
   public void   Interpret(ArrayList c)
   {
      int   n = 0;

      foreach (int i in c)   n += i;

      Console.WriteLine("==>  {0}", n);

      c.Clear();
      c.Add(n);   //   push result back onto context
      Console.WriteLine("...cleared stack, pushed {0}", n);
   }
}

If we call the parser from the client as follows:

C#
RPNParser.Parse("55 32 + 29 441 +")
We should expect to see the following results:
Parsing '55 32 + 29 441 +'
...pushed 55
...pushed 32
==>  87
...cleared stack, pushed 87
...pushed 29
...pushed 441
==>  557
...cleared stack, pushed 557

In this illustration, the syntax tree was simple, since the only non-terminal expression is composed of integer literal expressions, and the addition expressions. The parser (see the source code) splits up the input string and tries to create an integer literal. Failing that, it tries for an addition expression. All other input is ignored. Once the full input has been evaluated, it calls Interpret() on the non-terminal expression. With some modification (a real stack; a better parser; additional non-terminal expression classes,) one can create a fully functional RPN calculator which includes parenthesis and other fun RPN functionality. (I leave that as an exercise to the reader.)

Conclusions

Behavioral design patterns allow for great flexibility in how your software behaves:

The Chain of Responsibility frees objects from knowing which other objects handle a request, objects know the requests will be handled appropriately. The sender and receiver don't need explicit knowledge of each other. It can greatly expand the flexibility in what responsibilities you assign to objects. You do, however, have to be aware that there is no guarantee that a request will be handled, since there is no explicit receiver. It can "fall off the end of the chain..." (GOF)

The Command pattern allows one to):

  • Decouple the object that invokes an operation from the one that knows how to perform it.
  • Command objects can be extended like any other first-class object
  • You can assemble Command objects into a composite to create a "Macro Command"
  • New Commands can be created easily without changing existing classes
When using the Command pattern, there are decisions to be made:
  • How "deep" or "intellegent" is the command? The System.SqlClient.SqlCommand object is one extreme where it does much of the work by itself. Yours may simply bind a receiver and actions for the request.
  • How do you represent state for undo/redo, and how do you eliminate errors from accumulating? If you delete a file, how do you "undelete" it? What happens if it is deleted outside the control of your application?

The Interpreter design pattern helps us to "figure out" a language and represent its statements in so we can interpret them in code. It works best when your language is simple and when you're not overly concerned about efficiency. For complex languages, using a parser generator is a more viable option. Design Patterns has additional insights into the Interpreter pattern and nice examples in Smalltalk and C++.

Stay tuned for future articles...

Building the Samples

Unzip the source files to the folder of your choice. Start a shell (cmd.exe) and type nmake. You may have to alter the Makefile to point to the correct folder where your .NET Framework libraries exist.

History

2003-04-01 Corrected introduction

2002-11-14 Typo.Fix();

2002-11-13 Initial Revision

References

  • Design Patterns, Elements of Reusable Object-Oriented Software. Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Addison Wesley Longman, Inc. 1988. ISBN 0-201-63498-8.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer
United States United States
20+ years as a strategist at the intersection of business, design and technology.

Comments and Discussions

 
NewsInstead of ArrayList for Interpreter sample Pin
dzCepheus3-Sep-05 12:18
dzCepheus3-Sep-05 12:18 
Generalnot enough Pin
M. Kale4-Sep-03 0:53
M. Kale4-Sep-03 0:53 
GeneralDont need to go to the trouble Pin
Adam Turner2-Apr-03 12:36
Adam Turner2-Apr-03 12:36 
GeneralRe: Dont need to go to the trouble Pin
elnino2-Apr-03 14:09
elnino2-Apr-03 14:09 
GeneralRe: Dont need to go to the trouble Pin
Adam Turner2-Apr-03 17:23
Adam Turner2-Apr-03 17:23 
GeneralRe: Dont need to go to the trouble Pin
ian mariano3-Apr-03 1:08
ian mariano3-Apr-03 1:08 
GeneralRe: Dont need to go to the trouble Pin
Fakher Halim9-Jan-04 10:20
Fakher Halim9-Jan-04 10:20 
GeneralRe: Dont need to go to the trouble Pin
codesu18-Jul-04 21:30
codesu18-Jul-04 21:30 
GeneralRe: Dont need to go to the trouble Pin
drgonzo12021-Feb-06 0:06
drgonzo12021-Feb-06 0:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.