Professional Documents
Culture Documents
MSDN Design Pattern
MSDN Design Pattern
Microsoft has placed increasing emphasis on design patterns. If you are unfamiliar with patterns,
suddenly being inundated with new terms and foreign-looking UML diagrams can be overwhelming. This
emphasis on patterns, however, doesn't represent a shift in methodology so much as a change in
vocabulary. The Microsoft .NET Framework base class library (BCL) already makes extensive use of
patterns, and you are probably familiar with the most common ones, even though you might not realize it
yet.
In this article, I'll cover a basic overview of several common design patterns and how they are used in
the BCL and other areas of the .NET Framework. In doing so, you can discover some of the motivation for
why the Framework is designed the way it is, as well as make the abstract concepts of the patterns
themselves more intuitively understandable.
Most of the patterns I'll be covering come from the canonical reference, Design Patterns by Erich
Gamma, Richard Helm, Ralph Johnson, and John Vlissides, (Addison-Wesley, 1995). These authors are
collectively known as the Gang of Four. The Gang of Four didn't invent these patterns, but they
documented and formalized the good work others had been doing since the beginning of software
development.
If you are already familiar with some of these patterns, feel free to read about those you aren't familiar
with, since each section is relatively independent. The section on ASP.NET-related patterns requires
familiarity with the request pipeline, a basic overview of which is provided in that section.
Within the .NET Framework, the use of some patterns is so prevalent that they have been built into
programming languages themselves, instead of just represented by the class libraries. The first two
patterns I will discuss, the Observer and the Iterator, are supported by various language features of both
C# and Visual Basic .NET. As you will see, they are an integral part of many common programming tasks.
Observer Pattern
Good object-oriented design emphasizes both encapsulation and loose coupling. In other words, classes
should keep internal details private and also minimize their strict dependencies. In most applications,
classes don't work in isolation; they interact with many other classes. A common scenario of class
interaction occurs when one class (the Observer) needs to be notified when something changes in another
(the Subject). For example, several Windows Forms controls might need to update their display after a
button is clicked. A simple solution would be to have the Subject call a specific method of the Observer
whenever a change in state occurs. This introduces a host of problems, however. Now, since the Subject
needs to know which method to call, it is tightly coupled to that specific Observer. Furthermore, if you
need to add more than one Observer, you have to continue to add code for each method call to the
Subject. If the number of Observers changes dynamically, this gets even more complex. You'll quickly end
up with a brittle mess that's difficult to maintain.
Applying the Observer pattern helps to resolve this problem efficiently. You can decouple the Subject
from the Observers so that Observers of any variety can easily be added and removed, at both design
time and run time. The Subject maintains a list of interested Observers. Each time the Subject's state
changes, it calls the Notify method on each Observer. Figure 1 shows a sample implementation. All
classes designed to act as Observers implement the ICanonicalObserver interface, and all Subjects must
derive from CanonicalSubjectBase. If a new Observer wants to monitor a Subject, the Add method easily
handles this without having to change any code in the Subject class. Note also that each Subject only
directly depends on the ICanonicalObserver interface, not any specific Observer.
While the Gang of Four's Observer pattern solves some of these problems, there are still some
roadblocks, since subjects must inherit from a specific base class and Observers must implement a special
interface. Thinking back to the Windows Forms button example, a solution emerges. The .NET Framework
introduces delegates and events to solve these problems. If you have done any programming for ASP.NET
or Windows Forms, then you have probably worked with events and event handlers. Events act as the
Subject, while delegates act as Observers. Figure 2 shows an example of the Observer pattern, making
use of events.
The Windows Forms Button control exposes a Click event which gets raised whenever the button is
clicked. Any class designed to react to this event just needs to register a delegate with that event. The
Button class doesn't depend on any of the potential Observers, and each Observer only needs to know the
correct type of the delegate for the event (EventHandler, in this case). Since EventHandler is a delegate
type and not an interface, each Observer doesn't need to implement an extra interface. Assuming it
already contains a method with a compatible signature, it only needs to register that method with the
event of the Subject. Through delegates and events, the Observer pattern lets you decouple Subjects and
their Observers.
Back to Contents
Iterator Pattern
Many programming tasks involve manipulating collections of objects. Whether these collections are
simple lists or something more complex such as binary trees, you'll often need some way to get access to
each object in the collection. In fact, depending on the collection, you may want several ways to access
each object such as front to back, back to front, preorder or postorder. To keep the collection simple, the
traversal code itself is often in its own class.
One of the most basic ways to store a list of objects is in an array. Array types are built into both Visual
Basic .NET and C#. Both languages also have a loop structure that aids in iterating over arrays: foreach in
C# and For Each in Visual Basic .NET. Here's an easy example of iterating over arrays:
int[] values = new int[] {1, 2, 3, 4, 5};
foreach(int i in values)
{
Console.Write(i.ToString() + " ");
}
These statements make use of the iterator for the array behind the scenes. All you need to know is that
you are guaranteed to have the loop run exactly once for each item in the array.
To make those statements work, the object referenced in the In expression must implement
IEnumerable. Any collection of objects that implements the IEnumerable interface can be traversed
(enumerated). This interface has a single method, GetEnumerator, that returns an object which
implements IEnumerator. The IEnumerator class contains the code necessary to iterate through the
collection. It has a property for the current object (Current) and methods to advance to the next object as
well as start over (MoveNext, and Reset). All of the collection classes in the System.Collections
namespace, as well as arrays, implement IEnumerable and can therefore be iterated over.
If you examine the Microsoft intermediate language (MSIL) generated by the C# compiler for any code
that uses foreach, you can see that in most cases it just uses the IEnumerator to do the iteration (certain
types, such as arrays and strings, are special cased by the compiler). Here you can see the IEnumerator
approach for iterating over the same array shown previously:
int[] values = new int[] {1, 2, 3, 4, 5};
IEnumerator e = ((IEnumerable)values).GetEnumerator();
while(e.MoveNext())
{
Console.Write(e.Current.ToString() + " ");
}
The .NET Framework uses the IEnumerable and IEnumerator interfaces to implement the Iterator
pattern. The Iterator pattern lets you easily traverse a collection without exposing the inner workings of
that collection. An Iterator class, an implementer of IEnumerator, is a separate class from the collection,
which implements IEnumerable. The Iterator class maintains the state of the traversal (including what the
current item is and whether or not there are more items to be traversed) outside of the collection itself.
The algorithm for the traversal is contained in the Iterator as well. This way you can simultaneously have
multiple Iterators, each traversing the same collection in wildly different ways, without adding any
complexity to the collection class itself.
Back to Contents
Decorator Pattern
Any useful executable program involves either reading input, writing output, or both. Regardless of the
source of the data being read or written, it can be treated abstractly as a sequence of bytes. .NET uses
the System.IO.Stream class to represent this abstraction. Whether the data involves characters in a text
file, TCP/IP network traffic, or something else entirely, chances are you will have access to it via a Stream.
Since the class for working with file data (FileStream) and the class for working with network traffic
(NetworkStream) both inherit from Stream, you can easily write code that processes the data independent
of its origins. Here's a method for printing out some bytes from a Stream to the console:
public static void PrintBytes(Stream s)
{
int b;
while((b = fs.ReadByte()) >= 0)
{
Console.Write(b + " ");
}
}
Reading a single byte at a time is typically not the most efficient way to access a stream. For example,
hard drives are capable of (and optimized for) reading continuous blocks of data from the disk in a big
chunk. If you know you are going to be reading several characters, it is better to read a chunk from the
disk all at once and then consume the chunk from memory byte by byte. The Framework includes the
BufferedStream class for doing just that. The constructor for BufferedStream takes as the parameter
whatever stream you would like buffered access to. BufferedStream overrides the main methods of
Stream, such as Read and Write, to provide more functionality. Since it is still a child class of Stream, you
can use it the same as any other Stream (note that FileStream includes its own buffering capabilities).
Similarly, you can use System.Security.Cryptography.CryptoStream to encrypt and decrypt Streams on the
fly, without the rest of the application needing to know anything more than the fact that it is a Stream.
Figure 3 shows several calls to my printing method using different Streams.
Adapter Pattern
One of the strengths of the .NET Framework is backward compatibility. From .NET-based code you can
easily call legacy COM objects and vice versa. In order to use a COM component in your project, all you
have to do is add a reference to it via the Add Reference dialog in Visual Studio .NET. Behind the scenes,
Visual Studio .NET invokes the tlbimp.exe tool to create a Runtime Callable Wrapper (RCW) class,
contained in an interop assembly. Once the reference has been added (and the interop assembly has been
generated for you), the COM component can be used like any other class in managed code. If you were
looking at code someone else had written without seeing the list of references (and without examining
metadata associated with the classes or their implementation), you would be unable to tell which classes
were written in a .NET-targeted language and which were COM components.
The magic that makes this happen is contained in the RCW. COM components have different error
handling mechanisms and also make use of different data types. For example, strings in the .NET
Framework use the System.String class while COM might use a BSTR. When calling a COM component
with a string parameter from .NET-based code, though, you can pass in a System.String just like you
would to any other similar managed code method. Inside the RCW, this System.String is converted into a
format that the COM component expects, like a BSTR, before the COM call is made. Similarly, a method
call on a COM component typically returns an HRESULT to indicate success or failure. When a COM method
call returns an HRESULT that indicates that the call failed, the RCW turns this into an exception (by
default), so it can be handled like all other managed code errors.
By allowing managed classes and COM components to interact despite their interface differences, RCWs
are an example of the Adapter pattern. The Adapter pattern lets you adapt one interface to another. COM
doesn't understand the System.String class, so the RCW adapts it to something that it can understand.
Even though you can't change how a legacy component works, you can still interact with it. Adapters are
frequently used like this.
The Adapter class itself wraps an Adaptee, translating all calls from the client into the appropriate
format and sequence of calls. Though this sounds similar to the Decorator, there are several key
differences. With a Decorator, the interfaces of the objects you're composing are the same, while the
entire point of an Adapter is to allow you to change interfaces.Adapters also have a definite sequence to
them; the Adaptee must be contained by the Adapter. A Decorator class doesn't need to know whether it
is wrapped by 1 or 500 other classes, since the interfaces are all the same. As a result, the use of
Decorators can be transparent to the application, while the use of Adapter cannot.
Back to Contents
Factory Pattern
There are many cases in the Framework where you can obtain a new instance of a struct or class
without calling its constructor yourself. The System.Convert class contains a host of static methods that
work like this. To convert an integer to a Boolean, for example, you can call Convert.ToBoolean and pass in
the integer. The return value of this method call is a new Boolean set to "true" if the integer was non-zero
and "false" otherwise. The Convert class creates the Boolean for you with the correct value. Other type
conversion methods work similarly. The Parse methods on Int32 and Double return new instances of those
objects set to the appropriate value given only a string.
This strategy for creating new object instances is known as a Factory pattern. Rather than invoking the
object's constructor, you can ask the object factory to create the instance for you. That way, the factory
class can hide the complexity of object creation (like how to parse a Double out of a string). If you wanted
to change the details of creating the object, you'd only have to change the factory itself; you would not
have to change every single place in the code where the constructor is called.
These type conversion methods are one variant of this pattern, since you're not required to use the
factory to create the objects in question. A more pure example of this pattern is the
System.Net.WebRequest class, used to make a request and receive a response from a resource on the
Internet. FTP, HTTP, and file system requests are supported by default. To create a request, call the Create
method and pass in a URI. The Create method itself determines the appropriate protocol for the request
and returns the appropriate subclass of WebRequest: HttpWebRequest, FtpWebRequest (new to the .NET
Framework 2.0), or FileWebRequest. The caller doesn't need to know the specifics of each protocol, only
how to invoke the factory and work with the WebRequest that gets returned. If the URI changes from an
HTTP address to an FTP address, the code won't have to change at all. This is another common use of the
Factory pattern. The parent class acts as a factory and returns a specific derived class based on
parameters the client passes in. As in the WebRequest example, this hides the complexity of selecting an
appropriate derived class from the caller.
Back to Contents
Strategy Pattern
Both Array and ArrayList provide the capability to sort the objects contained in the collection via the
Sort method. In fact, ArrayList.Sort just calls Sort on the underlying array. These methods use the
QuickSort algorithm. By default, the Sort method will use the IComparable implementation for each
element to handle the comparisons necessary for sorting. Sometimes, though, it is useful to sort the same
list in different ways. For example, arrays of strings might be sorted with or without case sensitivity. To
accomplish this, an overload of Sort exists that takes an IComparer as a parameter; IComparer.Compare
is then used for the comparisons. This overload allows users of the class to use any of the built-in
IComparers or any of their own making, without having to change or even know the implementation
details of Array, ArrayList, or the QuickSort algorithm.
Leaving the choice of comparison algorithm up to the user of the class like this is an example of the
Strategy pattern. The use of Strategy lets a variety of different algorithms be used interchangeably.
QuickSort itself only requires a way to compare objects to each other. By calling Compare through a
provided interface, the caller is free to substitute whatever particular comparison algorithm fits its specific
needs. The code for the QuickSort can remain unchanged.
returns a Boolean. Combined with anonymous methods (another new C# language feature in version 2.0),
clients can easily filter lists based on properties and methods of the objects in the list, without introducing
dependencies into the List<T> class itself. Using the Strategy pattern lets complicated processes like
sorting be easily modified to fit a caller's specific purpose, meaning you'll be able to write and maintain
less code.
Back to Contents
When the standard library of ASP.NET controls doesn't meet your needs, you have several options on
how to create your own. For simple controls that only need to be used in a single project, a user control is
often the best choice. When the control is to be used in several Web applications or requires more
functionality, a custom server control may be a better fit.
When dealing with custom controls, there are two general types: controls that combine the
functionality of several existing controls (called composite controls), and controls with a unique visual
representation. The process for creating both of these types is very similar. For composite controls, you
create a new class that inherits from one of the control base classes (like Control or WebControl) and then
override the CreateChildControls method. In this method you add the controls whose functionality you are
combining to the collection of child controls, called Controls. For other custom controls, you override
Render instead and use the HtmlTextWriter parameter to output the HTML for your control directly.
Regardless of which style of custom control you choose, you don't have to write any code to handle the
functionality that's common to all controls, like loading and saving ViewState at the right time, allowing
PostBack events to be handled, and making sure the control lifecycle events are raised in the correct
order. The main algorithm for how a control should be loaded, rendered, and unloaded is contained in the
control base class.
The specifics of your particular control are handled at well-defined places in the control algorithm (the
CreateChildControls or Render methods). This is an example of the Template Method pattern. The main
algorithm skeleton is defined in a base class and subclasses can then plug in their own details without
affecting the algorithm itself, as shown in Figure 6. A composite control and a custom control both share
the same general lifecycle, but they can end up with drastically different visual representations.
When a client requests an ASPX page from the Web server, the request travels through many steps
before ultimately ending up as HTML displayed by the client's browser. First, the request is processed by
IIS and routed to the appropriate ISAPI extension. The ISAPI extension for ASP.NET (aspnet_isapi.dll)
routes the request to the ASP.NET worker process.
EndRequest. When the HttpApplication loads a module, it calls the Init method of the IHttpModule
interface, allowing the module to register for any of the events it cares about. As a given request is
handled, the events are raised in the appropriate order and all registered modules get a chance to interact
with the request. The module can therefore control the stage at which it gets invoked, but not the exact
order within that stage.
These modules are an example of the Intercepting Filter pattern. This pattern represents a chain of
filters that are each in turn given a chance to modify a request (or message) that passes through them.
Figure 8 shows a simplified flow diagram of this process. The key ideas of this pattern are that the filters
are independent; filters can modify the request as it passes through.
logical page in the application has a corresponding Web Form that controls its behavior and regulates its
presentation.
The practice of having one controller for each logical page is an example of the Page Controller pattern.
This idea is fundamental to ASP.NET. When a logical page is requested via its URI, the ASP.NET runtime
resolves the address to the corresponding subclass of Page and uses that class to handle the request. All
the details about what a page looks like, what user input it can handle, and how it responds to that input
are contained in a single place. When a logical page in the application requires changes, no other pages
are affected. It's an abstraction that's so common we don't even think about it.
One of the drawbacks usually associated with the pure implementation of Page Controller is that
common code must be repeated for each page. ASP.NET avoids this drawback by including other patterns
in the pipeline implementation, as well as providing System.Web.UI.Page as a common base class for all
Page Controllers. Cross-cutting concerns like authentication and session state are handled by the
HttpModule Intercepting Filters and raising page lifecycle events, and other activities, are handled by the
base class.
Back to Contents
Conclusion
Now that I have examined instances of common patterns in use throughout the .NET Framework and
BCL, it should be even easier to recognize those same patterns in code you work with every day. Hopefully
highlighting the design patterns underlying common classes and functionality has given you a better sense
of what those patterns are and the benefits they provide. Trying to imagine UI programming without the
Observer pattern or collections without an Iterator shows how indispensable these frameworks really are.
Once you understand what each does, it becomes another valuable tool to add to your toolbox.