Design Patterns - C#
Design Patterns - C#
SUMMARY A design pattern can solve many problems by providing a framework for building
an application. Design patterns, which make the design process cleaner and more efficient,
are especially well-suited for use in C# development because it is an object-oriented
language. Existing design patterns make good templates for your objects, allowing you to
build software faster. This article describes several popular design patterns you can use in
your own applications, including the singleton, the decorator, the composite, and the state
classes, which can improve the extensibility of your applications and the reuse of your objects.
https://docs.microsoft.com/en-us/archive/msdn-magazine/2001/july/design-patterns-solidify-your-csharp-application-architecture-with-design-patterns 1/12
7/3/2021 Design Patterns: Solidify Your C# Application Architecture with Design Patterns | Microsoft Docs
factors that limit their applicability to the solution in general. Together, the classes, the
communication and interconnections among those classes, and the contextual specifics
define a pattern that provides a solution to any problem in object-oriented software design
that presents characteristics and requirements matching those addressed by the pattern
context.
I must confess that I am an enthusiastic proponent of design patterns. Ever since I read
the seminal book Design Patterns by Gamma, Helm, Johnson, and Vlissides (Addison Wesley,
1995), I have rarely designed a feature that does not use any patterns. In fact, I spend
considerable time in the early stages of software design to identify patterns that would fit
naturally in the feature architecture. After all, patterns are time-tested and field-tested
solutions to problems that have been addressed by experienced architects, developers, and
language specialists, and it behooves anyone designing software to make use of the available
knowledge and expertise in this discipline. It is almost always a better idea to go with a
solution that has been proven successful time and again than to invent one completely from
scratch.
Few developers have the luxury of writing only small programs. Modern software
applications and systems are complex, comprising hundreds of thousands of lines of code,
and I know of code bases that are even larger. Programming demands a lot more than simple
mastery of tools and languages—corporate software development typically requires a great
deal of flexibility in design and architecture to accommodate the ever-changing needs of
clients and users at various stages of product development, and often after the product has
been released. Such dynamics dictate that software design not be brittle. It should be able to
accept changes without any undesirable ripple effect that would necessitate the reworking of
other, potentially unrelated, subsystems. It is frustrating and counterproductive to add
features and components to modules that were never designed for extensibility. Sooner or
later, closed, inflexible designs break under the weight of changes. Design patterns assist in
laying the foundation for a flexible architecture, which is the hallmark of every good object-
oriented design.
Design patterns have been cataloged to address a variety of design problems, from small
issues to large, architecture-level problems. In this article, I will describe some of the popular
design patterns that I have found useful in my own projects. The article does not assume any
prior knowledge of design patterns, although familiarity with concepts of object-oriented
design will help. While any programming language that facilitates object-oriented
development could be used to illustrate patterns, I will present examples exclusively in C#,
exposing some of the strengths of the language along the way. I will not discuss any
Microsoft® .NET-specific classes or libraries—instead, I'll focus on C# as a vehicle for
designing object-oriented software.
https://docs.microsoft.com/en-us/archive/msdn-magazine/2001/july/design-patterns-solidify-your-csharp-application-architecture-with-design-patterns 2/12
7/3/2021 Design Patterns: Solidify Your C# Application Architecture with Design Patterns | Microsoft Docs
programmers is flat. Even if you haven't seen any C# code before, you should have no
problem comprehending the example code in this article. In fact, I wouldn't be surprised if
you find the C# implementation of the design patterns cleaner, especially if you have used or
coded the patterns before. Books and articles that discuss design patterns typically explain
the problem and the context in great detail, followed by a formal description of the solution. I
will take a less rigorous approach in this article, focusing on the essence of the pattern
instead, and illustrating an appropriate example with some sample code in C#.
Singleton
Anyone who has ever written an MFC application—no matter how small—knows what a
singleton is. A singleton is the sole instance of some class. To use an MFC analogy, the global
instance of the CWinApp-derived application class is the singleton. Of course, while it's
imperative that no additional instances of the application class be created, there really is
nothing preventing you from creating additional instances. In situations like these, when you
need to enforce singleton behavior for a specific class, a better alternative is to make the class
itself responsible for ensuring that one and only one instance of the class can be created.
Back in the MFC analogy, you see that the responsibility for keeping track of the solitary
instance of the application class rests with the developers of the application. They must not
inadvertently instantiate another application object.
Now consider the class shown in Figure 1 . Note how access to the singleton is
controlled via the static method Instance. It is most often the case that the singleton should
also be globally accessible, and this is achieved by making the creation method public.
However, unlike the scenario in which a global variable is instantiated as the singleton, this
pattern prevents creation of any additional instances, while simultaneously allowing global
access. Note that the class constructor is private—there is no way to circumvent the static
method and directly create an instance of the class.
There are additional benefits, too. Specifically, this pattern can be extended to
accommodate a variable number of instances of an object. For instance, let's say you have an
application with a dedicated worker thread that is dispatched whenever a particular task is
required. In the interest of conserving system resources, you have implemented the thread as
a singleton. At some point along the way, if you decide to scale up your application because
the rate at which tasks arrive is too much for your singleton thread to handle, it will be fairly
straightforward to increase the number of worker threads in the application because all the
logic that creates the threads and grants access to them is confined to one class.
One other advantage to this pattern is that creation of the singleton can be delayed until
it is actually needed, as shown in Figure 1 . A variable declared at global scope will be
created on startup regardless of whether it is needed—it may very well be that the object isn't
always needed. C# doesn't allow variables at global scope anyway, but it is possible to create
an object on the heap at the outset of a method and not use it until much later, if at all. The
Singleton pattern offers an elegant solution in such cases.
https://docs.microsoft.com/en-us/archive/msdn-magazine/2001/july/design-patterns-solidify-your-csharp-application-architecture-with-design-patterns 3/12
7/3/2021 Design Patterns: Solidify Your C# Application Architecture with Design Patterns | Microsoft Docs
runtime. This is a significant benefit, as all you need to do in the C# version is make sure you
have a live reference to the singleton object for as long as it's needed.
Strategy
Applications are often written so that the way they perform a particular task varies,
depending on user input, the platform it's running on, the environment it's been deployed in,
and so on. An example is asynchronous I/O on disk files: Win32® APIs under Windows NT®
and Windows® 2000 support asynchronous I/O natively. However, that's not the case with
Windows 95 or Windows 98. An application that relies on asynchronous file I/O, therefore, has
to execute two different algorithms, depending on the deployment platform—one that uses
native Win32 APIs, and another that is built from scratch, perhaps using multiple threads.
Clients of such a service will be oblivious to the fact that different algorithms are being
executed; as far as they are concerned, the end result is the same and that's all they care
about.
Another example is downloading a file from some remote server on the Internet. An
application that offers a file download service that accepts a URL as input needs to examine
the URL, identify the protocol (FTP or HTTP, for example), and then create an object that can
communicate with the remote server using that protocol. Note that depending on user input,
a different algorithm (protocol) will be used. However, again, the end result is the same—a file
is downloaded.
Let's consider a more concrete example: testing for primality. The following code declares
an interface, a C# construct, with just one method: IsPrime.
= Copy
= Copy
= Copy
= Copy
interface Strategy
https://docs.microsoft.com/en-us/archive/msdn-magazine/2001/july/design-patterns-solidify-your-csharp-application-architecture-with-design-patterns 4/12
7/3/2021 Design Patterns: Solidify Your C# Application Architecture with Design Patterns | Microsoft Docs
An interface is like a contract. It is a specification that inheriting classes must follow. More
specifically, it defines method signatures but no implementations—the latter must be
provided by the concrete classes that implement the interface. C# is clearly superior to C++ in
this regard because C++ lacks native language support for interfaces. C++ programmers
typically create interfaces by defining abstract classes with pure virtual methods. In C#, all
interface members are public, and classes adhering to an interface must implement all
methods in the interface.
Now let's assume that I have three different algorithms for primality testing, each with its
own performance/accuracy trade-off. One of them is very computation-intensive, but does a
more thorough job of checking for factors, while another is faster but generates results that
may be inaccurate for very large numbers. My application will ask the user for the desired
performance and then invoke the appropriate algorithm. To this end, I will encapsulate my
algorithms in classes that implement the Strategy interface. Here's an example.
= Copy
= Copy
= Copy
= Copy
= Copy
= Copy
= Copy
= Copy
= Copy
= Copy
= Copy
https://docs.microsoft.com/en-us/archive/msdn-magazine/2001/july/design-patterns-solidify-your-csharp-application-architecture-with-design-patterns 5/12
7/3/2021 Design Patterns: Solidify Your C# Application Architecture with Design Patterns | Microsoft Docs
return result;
https://docs.microsoft.com/en-us/archive/msdn-magazine/2001/july/design-patterns-solidify-your-csharp-application-architecture-with-design-patterns 6/12
7/3/2021 Design Patterns: Solidify Your C# Application Architecture with Design Patterns | Microsoft Docs
Having implemented all three algorithms in this manner, I can now design the client in a
manner that decouples it from the implementation details of any specific algorithm. The client
holds a reference to the interface, and does not need to know anything about the concrete
implementation of the interface.
= Copy
= Copy
= Copy
= Copy
= Copy
= Copy
= Copy
= Copy
= Copy
= Copy
= Copy
= Copy
class Primality
https://docs.microsoft.com/en-us/archive/msdn-magazine/2001/july/design-patterns-solidify-your-csharp-application-architecture-with-design-patterns 7/12
7/3/2021 Design Patterns: Solidify Your C# Application Architecture with Design Patterns | Microsoft Docs
public Primality(Strategy s)
strategy = s;
return strategy.IsPrime(n);
https://docs.microsoft.com/en-us/archive/msdn-magazine/2001/july/design-patterns-solidify-your-csharp-application-architecture-with-design-patterns 8/12
7/3/2021 Design Patterns: Solidify Your C# Application Architecture with Design Patterns | Microsoft Docs
Finally, I create an instance of the Primality class and, depending on user input, initialize it
with the appropriate algorithm. The Test method of the Primality class invokes the IsPrime
method of the Strategy interface that every algorithm implements.
Decorator
A client application often needs to augment the services provided by methods of some
class, perhaps by inserting some preprocessing and post-processing tasks before and after
the method calls, respectively. One way to accomplish this is to bracket each method
invocation with calls to functions that achieve the desired effect. However, this approach is
not only cumbersome, it also limits the framework's extensibility. For instance, if distinct pre-
and post-processing tasks were to be carried out for different clients, the application logic
would be obscured by conditional statements, leading to a maintenance nightmare. The
question, then, is how to enhance the functionality offered by a class in a manner that does
not cause repercussions in client code. The Decorator pattern is just what's needed.
Consider a class with methods that clients can invoke to transfer files to and from some
remote server. Such a class might look like the one in Figure 2 .
Next, let's consider a client interested in this functionality. In addition to being able to
upload and download files, the client application would also like to log all file transfer
requests and perform access checks for each invocation. An implementation based on the
Decorator pattern would derive a class from FileTransfer and override the virtual methods,
inserting the additional operations before and after calling the base methods (see
Figure 3 ).
The client continues to work with the same interface. In fact, the solution can be improved
if the FileTransfer class, as well as the Decorator class, implement a common interface with the
Upload and Download methods. Doing so will allow the client to work exclusively in terms of
the interface and decouple it completely from a concrete implementation.
The Decorator pattern thus allows dynamic and transparent addition and removal of
responsibilities without affecting client code. It is particularly useful when a range of
extensions or responsibilities can be applied to existing classes, and when defining subclasses
to accommodate all those extensions is impractical.
Composite
The Composite pattern is useful when individual objects as well as aggregates of those
objects are to be treated uniformly. An everyday example is enumerating the contents of a file
folder. A folder may contain not only files, but subfolders as well. An application designed to
https://docs.microsoft.com/en-us/archive/msdn-magazine/2001/july/design-patterns-solidify-your-csharp-application-architecture-with-design-patterns 9/12
7/3/2021 Design Patterns: Solidify Your C# Application Architecture with Design Patterns | Microsoft Docs
recursively display the list of all files in some top-level folder can use a conditional statement
to distinguish between files and directories, and traverse down the directory tree to display
the names of files in the subfolders. A better approach is suggested by the Composite
pattern. In this approach, every folder item, be it a file, a subfolder, a network printer, or a
moniker for any directory element, is an instance of a class that conforms to an interface
offering a method to display a user-friendly name of the element. In this case, the client
application does not have to treat each element differently, thereby reducing the complexity
of the application logic.
Another example, and one that I will present in C#, is a drawing application that pulls
graphics primitives and blocks from an object database and paints them on a canvas. Let's
assume that the database can contain lines, circles, and drawings made up of these primitives.
Now consider the following interface.
= Copy
= Copy
= Copy
= Copy
interface Shape
void Draw();
The Shape interface contains one method, Draw. A simple graphics object such as a line
can implement this interface and override the Draw method to paint a line on some canvas, as
you can see in Figure 4 .
The circle primitive can similarly override the Draw method in the Shape interface and
https://docs.microsoft.com/en-us/archive/msdn-magazine/2001/july/design-patterns-solidify-your-csharp-application-architecture-with-design-patterns 10/12
7/3/2021 Design Patterns: Solidify Your C# Application Architecture with Design Patterns | Microsoft Docs
produce the desired shape on the canvas. In order to treat aggregates as well as simple
entities uniformly, however, objects of aggregates should also implement the Shape interface.
A drawing is a collection of graphics objects, and its implementation of the Draw method will
enumerate all primitives it contains and draw each one of them. Figure 5 shows how it
works.
Note that the client couldn't care less about whether an object is an instance of a graphics
primitive or a collection of such entities. A common interface across individual elements and
aggregates streamlines processing and allows you to add new objects without triggering any
modifications in the client application.
By presenting a shared interface across all components, the Composite pattern promotes
code reuse and simplifies client logic considerably by removing the burden of having to make
a distinction between individual elements and container objects.
Note that the implementation of Drawing.Draw uses the collection classes available in the
System.Collections library. For more information on these and other libraries, check out the
documentation in the .NET Framework SDK.
State
Every developer has implemented a finite state machine at least once in his or her career.
You can't avoid them—they are everywhere, and not just limited to the world of software
development. It's no wonder that literature on the design and implementation of
deterministic finite automata is also readily available. Given the ubiquity of finite state
machines, I am often surprised to see poor designs and buggy implementations with
hardwired transitions and total disregard for extensibility. The ability to add more states to
the design of a finite automaton is often an unwritten requirement, and implementations are
frequently modified when requests for additional states and transitions arrive. If you've got a
good design, you can anticipate and account for such changes. More importantly, behavior
and operations specific to any state in a finite state machine should be confined and localized
to the representation of that state. In other words, state-specific code lives in an object that
implements that state. This, in turn, allows you to add new states and transitions easily.
A popular design for finite state machines is based on table lookup. A table maps all
possible inputs for each state to transitions that would lead the machine to perhaps a
different state. Needless to say, while this design is simpler, it is unable to accommodate
changes without significant modifications to the existing implementation. A better alternative
is the solution offered by the State design pattern.
All five states will be subclasses of this base class and override its methods appropriately.
For instance, when the vending machine is in the Start state and a nickel is inserted, the
machine moves to the Five state. If another nickel is inserted, it moves to the Ten state. This
isolates the transition logic specific to each state in the corresponding object. The
https://docs.microsoft.com/en-us/archive/msdn-magazine/2001/july/design-patterns-solidify-your-csharp-application-architecture-with-design-patterns 11/12
7/3/2021 Design Patterns: Solidify Your C# Application Architecture with Design Patterns | Microsoft Docs
The vending machine does not keep track of the transition logic—it forwards the
operation to the instance of its current state, hence decoupling itself from state-specific
behavior, as you can see in Figure 8 .
I have already described how such a design is superior to a simple, table-based
implementation. To summarize, the State design pattern helps localize state-specific behavior
to classes that implement concrete states, which promotes reuse and extensibility. This
removes the need for conditional statements that would otherwise be scattered throughout
the code, making life difficult for maintenance programmers, who vastly outnumber
implementors in the real world.
Conclusion
Design patterns distill years of experience in solutions to frequently encountered
problems in the design of object-oriented software. They offer answers to questions that
most software developers face regardless of the size or scope of the project. C# promises to
enhance programmer productivity with its features that promote object-oriented design,
while removing the onus of certain housekeeping chores from the developer. Together, they
make a winning combination.
Complete C# code listings for examples in this article are available at the link at the top of
this article.
Design Patterns by Gamma, Helm, Johnson, and Vlissides (Addison Wesley, 1995)
Samir Bajaj is a senior software developer in the Design Platforms Group at Autodesk Inc. Samir
contributes to the design and implementation of Internet-based features. He coauthored Inside
AutoCAD 2000 (New Riders, 1999). He can be reached at samir.bajaj@autodesk.com.
https://docs.microsoft.com/en-us/archive/msdn-magazine/2001/july/design-patterns-solidify-your-csharp-application-architecture-with-design-patterns 12/12