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

Illustrated GOF Design Patterns in C# Part V: Behavioral II

Rate me:
Please Sign up or sign in to vote.
4.87/5 (19 votes)
31 Mar 2003CPOL9 min read 136.1K   496   173   9
Part V 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 V of a series of articles illustrating the GOF Design Patterns in C#. We will discuss the Iterator, Mediator, Memento, Observer, and State patterns. The next article will conclude the C# illustrations of the GOF design patterns as well as 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 together. The first three articles in this series dealt with the Creational and Structural patterns, and the last article introduced the Behavioral patterns. This article continues 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."

Iterator

The intent of the Iterator pattern according to the GOF is to "Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation." C# is already rife with iterators, so I am only going to give an overview. The structure looks like this:

Iterator structure

Many of the .NET System.Collections assembly classes support all the functionality of the Iterator pattern through a call to GetEnumerator(), which returns a concrete iterator, the IEnumerator interface. I will not illustrate usage of the .NET "iterator," for there are plenty of examples that abound.

Mediator

When designing systems, a problem often occurs when there are many objects making many calls to each other. There is a great potential for things to get "lost in the shuffle." This is especially true months or years down the road when systems get refactored / updated. Things break rather easily because things are tightly coupled. The Mediator pattern is useful because it allows you to keep objects from referring to each other directly, and "it lets you vary their interaction independently." (GOF) The structure of the Mediator pattern looks like this:

Mediator structure

For our illustration (mediator.cs,) suppose we consider an aviation manufacturer's robot control process. There are several robots, which report activity, errors, need for activity, etc. The robots need to communicate with others in order to change their state, for example, if an operational error occurs, or a subsystem has been completed. Because of the various number of robots and unrelated messages, the current system has become quite ugly to maintain. The Mediator pattern allows us to simplify the interactions. We'll define our base class robot, which knows it's "director":

C#
public class   BaseRobot
{
   protected RobotDirector   m_Director;

   public   BaseRobot(RobotDirector d)
   {
      m_Director = d;
   }

   public void   OperationalError(string reason)
   {
      m_Director.ErrorOccurred(this, reason);
   }

   //   ...
}

We now define our concrete Robots, and RobotDirector which contains Robot references (for notifications) and channels Robot interaction:

C#
public class   RobotDirector
{
   protected WelderRobot   m_Welder;
   protected PainterRobot   m_Painter;
   protected AssemblyRobot   m_Assembly;

   public void   Initialize()
   {
      m_Welder = new WelderRobot(this);
      m_Painter = new PainterRobot(this);
      m_Assembly = new AssemblyRobot(this);
   }

   public void   DoWork()
   {
      m_Painter.DoWork();
      m_Welder.DoWork();
   }

   public void   ErrorOccurred(BaseRobot r, string why)
   {
      Console.WriteLine("Error Occurred from {0}:  {1}",
         r.GetType().ToString(), why);

      //   tell other robots to shut down
      m_Welder.Shutdown();
      m_Painter.Shutdown();
      m_Assembly.Shutdown();
   }
}

The responsibility of inter-object communication has been moved to the director, which in turn mediates the object interaction. Objects involved need not "know" how to interact with other objects in the same director's arena, only that they need to communicate with the director. This allows a great deal of flexibility.

Memento

Object state is a tricky thing in software development. An object needs to save or retrieve its state, but in doing so may expose itself, revealing implementation details and breaking encapsulation. The Memento pattern is useful in that it stores a snapshot of state from its originator, and only the originator can directly access the stored state. The memento is opaque to other objects. The pattern structure:

Memento structure

The Caretaker knows nothing except how to store and manage Memento objects. The Originator can access or create a Memento to preserve its state. The Memento can contain as much or as little state as required by the Originator. Using this pattern, we can assign (recall) arbitrary states of an object. Unfortunately, in C#, there is no concept of friend access as there is in C++, so opacity to other objects is unreachable. A separate state object in C# must expose methods to the originator, and in doing so, it exposes these methods to all other objects. I will not illustrate this pattern due to this limitation. One could declare the state member fields or properties as internal, but the members would still be exposed to all objects within a program.

Observer

Object state changes, and it is often necessary for other objects to be notified when this happens. The Observer behavioral design pattern is used for just that. Imagine a data structure which can be represented by several graphical views, when the data structure changes, so must the views. The pattern looks like this:

Observer structure

C# is well-suited for this pattern because of delegates and events, inherently supporting the Observer pattern. For our example (observer.cs), a business has a purchase order editing system has several objects which watch for price changes on various products. Some of these objects may be user interface, some may be business objects which operate on open purchase orders. We define the delegate for the price change:

C#
public delegate void   PriceChanged(BaseProduct p);

In our base product class, we define the "observer" storage for event notification:

C#
public event PriceChanged   PriceChangedObservers;

We add notification of the observers to the price property of our base product:

C#
public double   price
{
   get   {   return m_yPrice;   }
   set
   {
      m_yPrice = value;

      //   notify listeners
      PriceChangedObservers(this);
   }
}

Our observers merely implement the event and add themselves to the "list" of observers for price:

C#
public class   BaseObserver
{
   public void OnPriceChanged(BaseProduct p)
   {
      Console.WriteLine("{0} detected price change on {1}, ${2}",
         this.GetType().ToString(), p.name, p.price);
   }

   public void   ObserveProduct(BaseProduct p)
   {
      p.PriceChangedObservers += new PriceChanged(this.OnPriceChanged);
   }
}

We now have an observer framework we can use in the purchase order system, which should output the following:

Concrete.UIObserver detected price change on Product A, $24.99
Concrete.UIObserver updating UI view
Concrete.POObserver detected price change on Product A, $24.99
Concrete.POObserver updating PO pricing
Concrete.UIObserver detected price change on Product B, $11.99
Concrete.UIObserver updating UI view
Concrete.POObserver detected price change on Product B, $11.99
Concrete.POObserver updating PO pricing

State

Objects often change their behavior when their internal state changes. Rather than using a plethora of conditional statements, the State pattern allows "plug in" behavior. For example, a vending machine may allow purchase and dispensing of an item depending on how much money has been given. The State pattern structure looks like this:

State structure

For our illustration (state.cs), a business uses an Order object which can load order details "on the fly." Calculation of order totals depends on whether or not the details are loaded. We use the State pattern to define the behavior of this calculation.

We store a reference to a "base order state" in our Order context, and delegate order operations to the reference:

C#
public class   OrderContext
{
   private BaseOrderState   m_State;
   private int   m_nOrderID;

   public   OrderContext(int id)
   {
      m_State = new OrderUnloadedState();
      m_nOrderID = id;
   }

   public int   OrderID   {   get   {   return m_nOrderID;   }   }

   public void   ChangeState(BaseOrderState s)
   {
      m_State = s;
   }

   public void   Load()   {   m_State.Load(this, m_nOrderID);   }

   public double   CalcSubtotal()
   {
      if (!m_State.IsLoaded())   Load();

      return m_State.CalcSubtotal(this);
   }

   public double   CalcTax()
   {
      if (!m_State.IsLoaded())   Load();

      return m_State.CalcTax(this);
   }

   public double   CalcTotal()
   {
      if (!m_State.IsLoaded())   Load();

      return m_State.CalcTotal(this);
   }
}

Notice that we allow "just in time" loading of order details in the Order Context, while the State can perform this JIT loading, it's more desirable to have the Context test the state for whether or not the state "is loaded."

Our BaseOrderState defines the operations whose behavior changes according to state, and our derived classes implement the proper behavior, including changing the state of the OrderContext:

C#
public class   OrderLoadedState : BaseOrderState
{
   //   we'd load these from a DB in real life
   public const double   TAX = 0.07;
   public const double   SUBTOTAL = 99.25;

   override public void   Load(OrderContext c, int id)
   {
      OrderLoadedState   s = new OrderLoadedState();

      //   we would load the details from the DB at this point
      Console.WriteLine("Re-Loaded order details for order id {0}", id);

      ChangeState(c, s);
   }

   override public bool   IsLoaded()   {   return true;   }

   override public double   CalcSubtotal(OrderContext c)  <BR>                                              {   return SUBTOTAL;   }
   override public double   CalcTax(OrderContext c)   <BR>                                              {   return SUBTOTAL * TAX;   }
   override public double   CalcTotal(OrderContext c)   <BR>                                  {   return SUBTOTAL + (SUBTOTAL * TAX);   }
}

Conclusions

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

The Iterator pattern simplifies the traversal of collections.

The Mediator pattern allows you to simplify interactions between large numbers of objects, decoupling them from each other; promotes better object reuse and customization by reducing explicit "links" to other objects. It also reduces subclassing and simplifies object protocols, easing maintenance; abstracts how objects communicate letting you focus on how objects interact independent from their behavior apart; and centralizes control.

The Memento pattern is useful in languages (such as C++) where the concept of friend access is supported, in order to provide a mechanism to store object state without breaking encapsulation rules.

The Observer pattern is powerful and easy to implement in C# compared to other languages because of the inherent support for delegates and events. It is easy to observe many subjects by adding event handlers to the observers. There are nuances to this pattern which you should review in the GOF Design Patterns book, such as ensuring the subject's state is consistent, or where the notification is triggered. The Observer pattern allows abstraction of subject/observer coupling and broadcast communication. A downside is that notifications may send a deluge of updates to observers, which is why you should use a well-defined notification event and handler instead of a broad "subject changed" notification.

The State pattern allows localization of state-specific behavior, allowing for easy addition of new states and transitions by adding subclasses. The transitions become explicit, and protects the context from inconsistent states because the transitions are atomic from the point of view of the context. The state objects can also be shared amongst contexts. The drawback is that you may end up with a lot of classes.

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-12-07 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

 
GeneralMediator pattern problem Pin
C Is Sharp28-Dec-10 5:48
C Is Sharp28-Dec-10 5:48 
GeneralExcellent articles Pin
Dhanya19776-Apr-05 6:02
Dhanya19776-Apr-05 6:02 
GeneralRe: Excellent articles Pin
ian mariano6-Apr-05 7:15
ian mariano6-Apr-05 7:15 
GeneralState Pattern CodeSmith Template (C#) Pin
Axel Rietschin13-Jan-05 0:20
professionalAxel Rietschin13-Jan-05 0:20 
GeneralBAD UML diagrams Pin
M. Kale22-Jun-03 23:23
M. Kale22-Jun-03 23:23 
GeneralRe: BAD UML diagrams Pin
Anonymous13-Jan-05 2:41
Anonymous13-Jan-05 2:41 
GeneralBaaaad. Pin
Anonymous7-Jun-03 14:17
Anonymous7-Jun-03 14:17 
GeneralRe: Baaaad. Pin
ian mariano7-Jun-03 19:00
ian mariano7-Jun-03 19:00 
GeneralRe: Baaaad. Pin
drgonzo12020-Feb-06 23:58
drgonzo12020-Feb-06 23:58 

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.