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

Illustrated GOF Design Patterns in C# Part VI: Behavioral III

Rate me:
Please Sign up or sign in to vote.
4.93/5 (17 votes)
26 Mar 2003CPOL8 min read 118.2K   468   182   6
The Final installment 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 the conclusion of a series of articles illustrating the GOF Design Patterns in C#. We will discuss the Strategy, Template Method, and Visitor patterns. I will also provide some closing remarks. 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 act. The first three articles in this series dealt with the Creational and Structural patterns, and the last article continued the Behavioral patterns. This article concludes 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."

Strategy

Often in software development, actions an object must take depend on the context in which they are performed. For example, a text-rendering engine may lay out text as plaintext in one case, rich text in another, and as cells in a spreadsheet in yet another. While the activity is the same, composition, the algorithm used varies. The Strategy behavioral design pattern is useful in that it "define[s] a family of algorithms, encapsulate[s] each one, and make[s] them interchangeable. Strategy lets the algorithm vary independently from the clients that use it." (GOF). The GOF suggest use of this pattern when

  • many related classes differ only in their behavior. Strategies provide a way to configure a class with one of many behaviors.
  • you need different variants of an algorithm. For example, you might define algorithms reflecting different space/time trade-offs. Strategies can be used when these variants are implemented as a class hierarchy of algorithms.
  • an algorithm uses data that clients shouldn't know about. Use the Strategy pattern to avoid exposing complex, algorithm-specific data structures.
  • a class defines many behaviors, and these appear as multiple conditional statements in its operations. Instead of many conditionals, move related conditional branches into their own Strategy class.

The structure of this pattern appears thus:

Strategy structure

For our simple example (strategy.cs), our Context will be a list of strings, and we wish to output this list using two specific algorithms: unsorted and sorted. We create the abstract strategy:

C#
public interface   IOutputStrategy
{
   void   Output(ArrayList l);
}

Our concrete implementations will perform the actual unsorted and sorted output algorithms:

C#
public class   UnsortedStrategy : IOutputStrategy
{
   public void   Output(ArrayList l)
   {
      Console.Write("(");

      for(int i = 0; i < l.Count; i++)
      {
         Console.Write(l[i]);

         if (i + 1 < l.Count)   Console.Write(", ");
      }

      Console.WriteLine(")");
   }
}

//  SortedStrategy is similar

Our Context supports assignment of a Strategy in the constructor, as well as later on, and a way to create our string list by Splitting, delimited by the space character:

C#
public class   ListContext
{
   private ArrayList   m_List;
   private IOutputStrategy   m_Strategy;

   public ListContext(IOutputStrategy strategy)
   {
      m_Strategy = strategy;
   }

   public void   SetStrategy(IOutputStrategy s)
   {
      m_Strategy = s;
   }

   public void   MakeList(string s)
   {
      m_List = new ArrayList(s.Split(' '));
   }

   public void   Output()   {   m_Strategy.Output(m_List);   }
}

Our client then creates a ListContext, with the appropriate IOutputStrategy implementation:

C#
ListContext   lc = new ListContext(new UnsortedStrategy());

lc.MakeList("This is a sample of varying the strategy");
lc.Output();
lc.SetStrategy(new SortedStrategy());
lc.Output();

Although this is a simple example, one can see that the Strategy pattern allows great flexibility in algorithm implementation and greatly simplifies object activity. We can "plug in" additional strategies quite easily.

Template Method

The Template Method pattern allows software to define a skeleton of an algorithm in an operation, deferring some steps to subclasses. We see this all the time in virtual functions in C#, therefore I shall not illustrate this pattern with a code example, as we have been using Template Methods throughout these articles. The Template Method structure:

Template Method structure

Visitor

The Visitor pattern "Represent[s] an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates." (GOF). It is possible using this pattern to separate the structure of an object, from the operations that act on the structure. This is useful in that new operations may be defined without modifying the original object. As a trivial example,a bank account object may be operated on for credit or debit purposes. Later, it is possible to add an "interest" operation, a "service fee" operation, or "transmit information to mobile device" operation without modifying the original object.

The GOF suggests using the Visitor pattern when

  • an object structure contains many classes of objects with differing interfaces, and you want to perform operations on these objects that depend on their concrete classes.
  • many distinct and unrelated operations need to be performed on objects in an object structure, and you want to avoid "polluting" their classes with these operations. Visitor lets you keep related operations together by defining them in one class. When the object structure is shared by many applications, use Visitor to put operations in just those applications that need them.
  • the classes defining the object structure rarely change, but you often want to define new operations over the structure. Changing the object structure classes requires redefining the interface to all visitors, which is potentially costly. If the object structure classes change often, then it's probably better to define the operations in those classes.

The Visitor pattern structure:

Visitor structure

For our example (visitor.cs), a business wants a new product inventory program which will allow reporting of various pieces of data: the business cost, possible revenue, and actual revenue of the inventory. We will simplify the example by not taking any date-related information into account. Products are the elements that need to be "visited." We define our base visitor as follows:

C#
public class   BaseSummingVisitor
{
   protected int   m_nNumProducts;
   protected double   m_ySum;

   public BaseSummingVisitor()
   {
      m_nNumProducts = 0;
      m_ySum = 0.0;
   }

   public void   Reset()
   {
      m_nNumProducts = 0;
      m_ySum = 0.0;
   }

   virtual public void   VisitProduct(Product p)   {   }

   virtual public void   Results()   {   }
}

We define our product, allowing for visitation:

C#
public class   Product
{
   private string   m_strName;
   private int   m_nOnHand;
   private int   m_nSoldToDate;
   private double   m_yCost;
   private double   m_yPrice;

   public Product(string name, int onHand, int sold, double cost, double price)
   {
      m_strName = name;
      m_nOnHand = onHand;
      m_nSoldToDate = sold;
      m_yPrice = price;
   }

   public string   name   {   get   {   return m_strName;   }   }
   public int   onHand   {   get   {   return m_nOnHand;   }   }
   public int   sold   {   get   {   return m_nSoldToDate;   }   }
   public double   cost   {   get   {   return m_yCost;   }   }
   public double   price   {   get   {   return m_yPrice;   }   }

   public void   Accept(BaseSummingVisitor v)
   {
      v.VisitProduct(this);
   }
}

We then create our concrete Visitors:

C#
public class   CostVisitor : BaseSummingVisitor
{
   public   CostVisitor() : base()   {   }

   override public void   VisitProduct(Product p)
   {
      m_nNumProducts += p.onHand;
      m_ySum += (p.onHand * p.cost);
   }

   override public void   Results()
   {
      Console.WriteLine("Total cost ({0} products):  ${1}", m_nNumProducts,
         m_ySum);
   }
}

//   ...

Our client would then iterate through the products, using the appropriate Visitor:

C#
ArrayList   l = new ArrayList();

l.Add(new Product("chair", 5, 2, 10.00, 24.99));
l.Add(new Product("desk", 10, 5, 45.00, 150.00));
l.Add(new Product("filing cabinet", 20, 7, 15.00, 45.00));

CostVisitor   cv = new CostVisitor();
SalesVisitor   sv = new SalesVisitor();
PotentialSalesVisitor   psv = new PotentialSalesVisitor();

foreach(Product p in l)
{
   p.Accept(cv);
   p.Accept(sv);
   p.Accept(psv);
}

cv.Results();
psv.Results();
sv.Results();

It would be fairly simple to add a new type of BaseSummingVisitor to our application, for example a TaxVisitor. This is a powerful design pattern, prone to misuse if not properly thought out. I encourage you to review its nuances in Design Patterns.

Conclusions

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

The Strategy pattern is useful because it allows great flexibility in client usage of a family of algorithms to accomplish a task. You can separate the data used in implementation of an algorithm from clients that does not need to know about, thereby reducing complexity, class bloat and areas of highly branched logic that would otherwise exist. One must recognize some drawbacks as well: clients must be aware of different strategies, may be exposed to implementation issues, and the variance in behavior may not be relevant to clients; there will be communication overhead between the Context and the Strategy; and there will be an increased number of objects in your system.

Template Methods allow us to (GOF):

  • to implement the invariant parts of an algorithm once and leave it up to subclasses to implement the behavior that can vary.
  • to control subclasses extensions. You can define a template method that calls "hook" operations at specific points, thereby permitting extensions only at those points.

The Visitor pattern allows separation of the structure of objects from the operations that act on them. You can add operations easily, gather related operations together and separate unrelated ones. There are some limitations: Adding new elements to be operated on can be difficult, because they may add new abstract operations which need to be implemented in each concrete visitor. One may also break encapsulation because of the need to operate on data contained within the visited elements. Please review the Design Patterns Visitor consequences for more information.

Some Closing Remarks

The GOF Design Patterns are only a subset of available patterns out there. They, as well as other design patterns, should not be treated as a complete solution for software development, rather as a set of guiding principles. Good programming is still paramount - without a good programming foundation, all the patterns in the world are useless. There are times to break from known patterns, to strike out on your own, leading you to new, innovative patterns or solutions to software design and implementation. Recognize and expect limitations while also recognizing opportunity...

As we head even further into the digital age, we'll be composing more complex and critical systems, systems upon which lives, economies, and entertainment depend. Armed with good programming and design skills, it should truly be a golden age.

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

2002-12-10 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

 
GeneralCoupling Product to Visitor Pin
Clint Edmonson18-Feb-04 11:20
Clint Edmonson18-Feb-04 11:20 
GeneralBridge vs. Strategy Pin
Peter V26-Mar-03 18:52
Peter V26-Mar-03 18:52 
GeneralRe: Bridge vs. Strategy Pin
ian mariano27-Mar-03 4:32
ian mariano27-Mar-03 4:32 
GeneralQuestion Pin
Anthony_Yio12-Jan-03 21:13
Anthony_Yio12-Jan-03 21:13 
GeneralRe: Question Pin
ian mariano13-Jan-03 6:31
ian mariano13-Jan-03 6:31 
GeneralRe: Question Pin
Anthony_Yio13-Jan-03 14:56
Anthony_Yio13-Jan-03 14:56 

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.