![]() |
Development Lifecycle »
Design and Architecture »
Design Patterns
Beginner
License: The Code Project Open License (CPOL)
Illustrated GOF Design Patterns in C# Part VI: Behavioral IIIBy ian marianoThe Final installment of a series of articles illustrating GOF Design Patterns in C# |
C#, Windows, .NET, Visual Studio, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
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.
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.
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."
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
The structure of this pattern appears thus:
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:
public interface IOutputStrategy
{
void Output(ArrayList l);
}
Our concrete implementations will perform the actual unsorted and sorted output algorithms:
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:
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:
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.
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:
The GOF suggests using the Visitor pattern when
The Visitor pattern 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:
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:
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:
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:
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.
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):
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.
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...
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.
2002-12-10 Initial Revision
| You must Sign In to use this message board. | |||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 26 Mar 2003 Editor: Chris Maunder |
Copyright 2002 by ian mariano Everything else Copyright © CodeProject, 1999-2009 Web22 | Advertise on the Code Project |