Click here to Skip to main content
Click here to Skip to main content

Design Pattern Examples in C#

, 7 Mar 2009
Rate this:
Please Sign up or sign in to vote.
Simple examples illustrate Design Pattern

Introduction

Design patterns are some common ideas in writing programs. The whole list and their definitions can be found here. The link has very good descriptions and examples, so I won't repeat the definitions here. I think that the design patterns are not easy to understand, and there are too many examples to remember. In order to apply the design patterns to solve a problem, you would need to understand and remember the patterns so that you could see them in the problem. I couldn't remember even half of the names; I couldn't accurately recite a single pattern definition; and I am not sure whether I fully understand the definitions of the design patterns. However, I have been helped by a simple example which makes the patterns easy to understand and remember. I would like to share it and hope that it can help other people, too.

Background

I took the training from NetObjectives several years ago. I don't remember much of the definitions or principles from the training, but their example sticks in my mind. It is a simple problem but it holds several patterns in the problem.

The example is this: We need to design classes for two Shapes: Rectangle and Circle. We need to draw them on different devices: Console and Printer. The shapes can be moved and expanded. In the future, we might need to support other forms of transformation, such as rotate and distort. After transforming the shapes, we need to be able to undo it. We need to support more complex shapes which are comprised of simpler shapes.

Patterns in this Example

We first abstract the Rectangle and Circle as Shape. If the Shapes know how to draw on Printer and Console, then we would need different versions of Shapes. So, it is better to separate the Shape from the Drawing devices.We also abstract the Console and Printer as Drawing devices. This is actually using Bridge pattern because that it decouples the implementation of Shape and Drawing. In the following snippet, the IShape holds a reference to IDrawing object. Shape has two implementations: Circle and Rectangle. Drawing has two implementations: V1 and V2.

/*
IShape------------->IDrawing
 /    \              / \
Circle Rectangle    V1 V2
*/
abstract class IShape
{
 protected IDrawing drawing;
 abstract public void Draw();
}
abstract class IDrawing
{
    abstract public void DrawLine();
 abstract public void DrawCircle();
}
class Circle:IShape
{
 override public void Draw(){drawing.DrawCircle();}
}
class Rectangle:IShape
{
 override public void Draw(){drawing.DrawLine(); drawing.DrawLine();}
}
class V1Drawing:IDrawing
{
 override public void DrawLine(){}
 override public void DrawCircle(){}
}
class V2Drawing:IDrawing
{
 override public void DrawLine(){}
 override public void DrawCircle(){}
}

Suppose that there are fixed number of Shapes, but there will be new types of Transformations. In order to support new types of Transformations, we can use the Visitor pattern to make Shapes class future proof. In the following snippet, the Shape class defines operation interface Accept(ITransform), so that new transform types can operated on shapes without modifying Shape classes.

/*
   IShape---------->ITransform
 /    \            / \
Circle Rectangle Move Expand
*/
    abstract class IShape
    {
        abstract public void Accept(ITransform transform);
    }
    abstract class ITransform
    {
        abstract public void TransformCircle(Circle c);
        abstract public void TransformRectangle(Rectangle rect);
    }
    class Circle : IShape
    {
        override public void Accept(ITransform transform)
        {
            transform.TransformCircle(this);
        }
    }
    class Rectangle : IShape
    {
        override public void Accept(ITransform transform)
        {
            transform.TransformRectangle(this);
        }
    }
    class Move : ITransform
    {
        override public void TransformCircle(Circle c) { }
        override public void TransformRectangle(Rectangle rect) { }
    }
    class Expand : ITransform
    {
        override public void TransformCircle(Circle c) { }
        override public void TransformRectangle(Rectangle rect) { }
    }

If we want to undo the transform, we can use Memento pattern, where we can store the state of the Shape object without exposing its internal state.

//Memento--->IShape

interface IMemento
{
 void ResetState();
}
class IShape
{
 public abstract IMemento GetMemento();
}

If we have a shape which is comprised of collections of other shapes, we can use Composite pattern so that we may handle the complex shape the same way.

/*        IShape
         / | \
    Circle | Rectangle
           |
    CompositeShape 1--->* IShape

*/

class CompositeShape : IShape
{
 List<IShape> list = new List<IShape>();
}

If you call MembewiseClone() on an object, you only get a shallow copy of the object. The book “C# 3.0 Design Patterns” has a solution for DeepCopy. It serializes the object into a MemoryStream, and then Deserializes it back to a cloned object.

[Serializable]
class IShape
{
public IShape DeepCopy()
{
 using (MemoryStream m = new MemoryStream())
 {
    BinaryFormatter f = new BinaryFormatter();
    f.Serialize(m, this);
    m.Seek(0, SeekOrigin.Begin);
    IShape ret = (IShape)f.Deserialize(m);
    ret.Drawing = drawing;
    return ret;
 }
}
}

Design Pattern Examples in C# and .NET Libraries

There are many examples in C# and .NET libraries. For example:

Adapter Pattern

Streams are tools to read/write a sequence of bytes. If you need to read/write strings or characters, you would need to create a Reader/Writer class. Fortunately, all the Reader/Writer classes can be constructed from a stream object. So, I think that the Reader/Writer classes are adapters which convert a byte array interface to string/char interface.

Reader/Writer-->Stream
BinaryReader/Writer{ReadChars,ReadString}
TextReader/Writer{ReadLine,ReadBlock}
|-StreamReader/Writer
|-StringReader/Writer
Stream{Read(byte[]), Write(byte[], Seek, Position)
|
|-NetworkStream(socket){DataAvailable}
|-FileStream(path){ Lock/Unlock/GetACL,IsAsync}
|-MemoryStream (byte[]){GetBuffer(), WriteTo(stream),ToArray()}

I think "boxing" is also an adapter. It converts a value type into a reference type.

Decorator Pattern

When you need encryption or compression, or need to add a buffer on the network stream, you can use Decorator pattern. The bufferedStream, CryptoStream, and DeflateStream are decorators to other streams. They attach additional functionalities to existing streams without changing the interface of the original streams.

Stream
|
|- BufferedStream(stream,buffersize), for network
|- CryptoStream: for encryption
|- DeflateStream: for compression

Flyweight Pattern

To save space, String class holds a reference to an Intern object. So, if two strings have the same literal, they share the same storage space. It uses "sharing" to support a large number of fine grained objects efficiently so that it is using Flyweight pattern.

String s3 = String.Intern(s2); 

Object Pool Pattern

Creating threads are expensive. You can call Threadpool.QueueUserWorkitem() which uses thread pool to better use the system resources. So, I think that threadpool is a good example of object pool pattern.

Observer Pattern

Observer pattern defines a one to many dependency so that when one object changes state, all its dependencies are notified. The event in C# is exactly for this purpose. An event can be subscribed:

UnhandledExceptionEventHandler handler =
	(object sender, UnhandledExceptionEventArgs v) => {
Console.WriteLine(
  "sender={0}, arg={1}, exception={2}, v.IsTerminating={3}",
  sender, v, v.ExceptionObject, v.IsTerminating);
  };
AppDomain.CurrentDomain.UnhandledException += handler;
throw new Exception("hiii");

To unsubscribe, you just need to call event-=.

AppDomain.CurrentDomain.UnhandledException -= handler;

You can also define your own events:

static public event EventHandler<EventArgs> breakingNews;
if(breakingNews!=null)
breakingNews("NBC", EventArgs.Empty);

If you don't want to check whether the event is null, just add a dummy subscriber:

breakingNews += delegate { }; 

Iterator Pattern

Iterator provides a way to access the elements inside an aggregate object. C# has foreach keyword which makes Iterator really easy. It is hard for me to understand what Enumerable and Enumerator are. It is even more confusing why I have to define GetEnumerator for both IEnumerable and IEnumerable<T>. So, I just try to remember this example:

class IntEnumerable:IEnumerable<int>
{
public IEnumerator<int> GetEnumerator()
{
    yield return 1;
    yield return 2;
}
IEnumerator IEnumerable.GetEnumerator()
{
    return this.GetEnumerator();
}
}
//Then you use the iterator like this:
foreach(int i in new IntEnumerable())Console.WriteLine(i);

Singleton Pattern

The simplest way to create a singleton is to use a static variable in C#.

class Singleton
{
    private static Singleton instance = new Singleton();
    public static Singleton GetInstance()
    {
        return instance;
    }
}

Some people use the double-checked-lock to create a thread safe lazy initialization singleton. However, it is so subtle to implement that some people consider it as Anti-pattern: [Read this].

Netobjectives has this method which I think is the best way to create a thread safe lazy init singleton. Note that the static constructor disables BeforeFieldInit flag (see this link). The inner class Nested avoids early init when other static fields or methods of Singleton are called.

class Singleton
{
    private Singleton() { }
    public static Singleton GetInstance()
    {
        return Nested.instance;
    }
    private class Nested
    {
        internal static Singleton instance = new Singleton();
        //static constructor to prevent beforefieldInit
        // see http://www.yoda.arachsys.com/csharp/beforefieldinit.html
        static Nested() {}
    }
} 

Using the Code

The sample code is written in C# 3.0.

Conclusion

I am sure I missed many patterns and might have misunderstood some in the above examples. If so, please let me know so that they can be corrected.

I have benefited from the examples I got from the training in NetObjectives. I felt that the training is an eye opener and I really learnt a lot. After a couple years, the only thing I still remember is their example about Shape, Drawing, and Transform. If you are not familiar with design patterns, I hope that this example gets you interested in reading Wikipedia or a book, or taking the training from NetObjectives. If you have taken their training or are familiar with design patterns, then the examples might help you quickly refresh your memory of design patterns.
One thing I learned is that if my code has a lot of copy/paste, or has a lot of switch/case statements, then I might need to think about using some design patterns.

I think the idea of design pattern is similar to Database normalization. During database normalization, we break one big table into several smaller tables, so that the total number of rows in tables is much smaller, and modification in one small table won't affect other tables. If we can separate/isolate the concepts in our problem, then we are likely applying the ideas of design pattern already, even if we don't remember the pattern names.

History

  • 23rd February, 2009: Initial post
  • 6th March, 2009: Article updated
    • Fixed a broken link
    • Fixed the snippet compilation errors
    • Removed some samples

License

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

About the Author

Dong Xiang
Web Developer
United States United States
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 PinmemberPeace ON20-Jul-13 1:06 
GeneralMy vote of 5 PinmemberRaj Champaneriya15-Sep-12 19:50 
GeneralMy vote of 5 Pinmemberpuneetmalviya2u15-Jun-12 21:03 
GeneralGood Article PinmemberImtiaz.Ahmed22-May-11 8:50 
Generalgood article PinmemberDonsw5-Mar-09 15:49 
GeneralRe: good article PinmemberDong Xiang5-Mar-09 17:19 
GeneralSome suggestions for improvement Pinmemberptmcomp3-Mar-09 9:04 
GeneralRe: Some suggestions for improvement PinmemberDong Xiang5-Mar-09 17:17 
Thanks for the information. After reading your links, I realized that my understanding of NullObject was wrong. I have mistaken nullable type as nullobject. So, I'll remove the reference of NullObject from the article.
 
I have a hard time to tell the difference b/w Strategy and Bridge. I think that in the function List.Sort(IComparer)), the IComparer is a strategy object. Because that I am not so sure about strategy, I'll remove it from the article, too.
 
Thanks for leting me learn something new. It is even better than taking a class or reading a book to get feedback from others.
GeneralRe: Some suggestions for improvement Pinmemberptmcomp15-Mar-09 3:07 
GeneralPlease make sure your code snippets are compilable PinmemberMahin Gupta2-Mar-09 18:41 
GeneralRe: Please make sure your code snippets are compilable PinmemberDong Xiang5-Mar-09 16:26 
GeneralMy vote of 2 PinmemberBalamurugan R A1-Mar-09 19:33 
GeneralNo better PinmemberPIEBALDconsult23-Feb-09 7:02 
GeneralRe: No better PinmemberDong Xiang24-Feb-09 20:25 
GeneralRe: No better PinmemberPIEBALDconsult24-Feb-09 21:00 
GeneralRe: No better Pinmembergimbatul25-Feb-09 23:04 
GeneralRe: No better PinmemberNugpot29-Mar-10 12:29 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 7 Mar 2009
Article Copyright 2009 by Dong Xiang
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid