1. What’s a Pattern, Anyway?
The word pattern
is one of those overloaded terms that morphed from its common usage to
assume a very specific meaning in computer science. According to the
dictionary, a pattern is a template or model that can be used to
generate things—any things. In computer science, we use patterns in
design solutions at two levels: implementation and architecture.
At the highest level, two main families of software patterns are recognized: design patterns and architectural patterns.
You look at design patterns when you dive into the implementation and
design of the code. You look at architectural patterns when you fly high
looking for the overall design of the system.
Let’s start with design patterns.
Note
A third family of software patterns is also worth a mention—refactoring patterns.
You look at these patterns only when you’re engaged in a refactoring
process. Refactoring is the process of changing your source code to make
it simpler, more efficient, and more readable while preserving the
original functionality. Examples of refactoring patterns are "Extract
Interface" and "Encapsulate Field." Some of these refactoring patterns
have been integrated into Visual Studio 2008 on the Refactor menu. You
find even more patterns in ad hoc tools such as Resharper. (For more
information, see http://www.jetbrains.com/resharper.)
We software professionals owe design patterns to an architect—a real
architect, not a software architect. In the late 1970s, Christopher
Alexander developed a pattern language with the purpose of letting
individuals express their innate sense of design through a sort of
informal grammar. From his work, here’s the definition of a pattern:
Each pattern describes a problem
which occurs over and over again in our environment, and then describes
the core solution to that problem, in such a way that you can use the
solution a million times over, without ever doing it the same way twice.
Nicely enough, although the definition was not written with software
development in mind, it applies perfectly to that. So what’s a design
pattern?
A design pattern is a known and well-established core
solution applicable to a family of concrete problems that might show up
during implementation. A design pattern is a core solution and, as
such, it might need adaptation to a specific context. This feature
becomes a major strength when you consider that, in this way, the same
pattern can be applied many times in many slightly different scenarios.
Design
patterns are not created in a lab; quite the reverse. They originate
from the real world and from the direct experience of developers and
architects. You can think of a design pattern as a package that includes
the description of a problem, a list of actors participating in the
problem, and a practical solution.
The primary reference for design patterns is GoF. Another excellent reference we want to recommend is Pattern-Oriented Software Architecture by Frank Buschmann, et al. (Wiley, 1996).
How to Work with Design Patterns
Here is a list of what design patterns are not:
-
Design patterns are not the verb and should never be interpreted dogmatically.
-
Design patterns are not Superman and will never magically pop up to save a project in trouble.
-
Design patterns are neither the dark nor the light side of the Force.
They might be with you, but they won’t provide you with any special
extra power.
Design patterns are just helpful, and that should be enough.
You don’t choose a design pattern; the most appropriate design
pattern normally emerges out of your refactoring steps. We could say
that the pattern is buried under your classes, but digging it out is
entirely up to you.
The wrong way to deal with design patterns is by going through a list
of patterns and matching them to the problem. Instead, it works the
other way around. You have a problem and you have to match the problem
to the pattern. How can you do that? It’s quite simple to explain, but
it’s not so easy to apply.
You have to understand the problem and generalize it.
If you can take the problem back to its roots, and get the gist of
it, you’ll probably find a tailor-made pattern just waiting for you. Why
is this so? Well, if you really reached the root of the problem,
chances are that someone else did the same in the past 15 years (the
period during which design patterns became more widely used). So the
solution is probably just there for you to read and apply.
What we normally do is stop reading after the first few pages
precisely where most books list the patterns they cover in detail
inside. Next, we put the book aside and possibly within reach. Whenever
we encounter a problem, we try to generalize it, and then we flip
through the
pages of the book to find a pattern that possibly matches it. We find
one much more often than not. And if we don’t, we repeat the process in
an attempt to come to a better generalization of the problem.
When we’ve found the pattern, we start working on its adaptation to
our context. This often requires refactoring of the code which, in turn,
might lead to a more appropriate pattern. And the loop goes on.
Note
If you’re looking for an online quick reference about design patterns, you should look at http://www.dofactory.com. Among other things, the site offers .NET-specific views of most popular design patterns.
Where’s the Value in Patterns, Exactly?
Many people would agree in principle that there’s plenty of value in
design patterns. Fewer people, though, would be able to indicate what
the value is and where it can be found.
Using design patterns, per se, doesn’t make your solution more
valuable. What really matters, at the end of the day, is whether or not
your solution works and meets requirements.
Armed with requirements and design principles, you are up to the task
of solving a problem. On your way to the solution, though, a systematic
application of design principles to the problem sooner or later takes
you into the immediate neighborhood of a known design pattern. That’s a
certainty because, ultimately, patterns are solutions that others have
already found and catalogued.
At that point, you have a solution with some structural likeness to a
known design pattern. It is up to you, then, to determine whether an
explicit refactoring to that pattern will bring some added value to the
solution. Basically, you have to decide whether or not the known pattern
you’ve found represents a further, and desirable, refinement of your
current solution. Don’t worry if your solution doesn’t match a pattern.
It means that you have a solution that works and you’re happy with that.
You’re just fine. You never want to change a winning solution!
In summary, patterns might be an end when you refactor according to them, and they might be a means
when you face a problem that is clearly resolved by a particular
pattern. Patterns are not an added value for your solution, but they are
valuable for you as an architect or a developer looking for a solution.
We said a lot about design patterns, but we haven’t shown a single
line of code or a concrete example. Patterns are everywhere, even if you
don’t realize it. As we’ll see in a moment, sometimes patterns are buried in the language syntax—in which case, we’ll call them idioms.
Have you ever needed to use a global object (or a few global objects)
to serve all requests to a given class? If you have, you used the
Singleton pattern. The Singleton pattern is described as a way to ensure
that a class has only one instance for which a global point of access
is required. Here’s an example:
public class Helpers
{
public static Helpers DefaultInstance = new Helpers();
protected Helpers() {}
public void DoWork()
{
.
.
.
}
public void DoMoreWork()
{
.
.
.
}
}
In a consumer class, you take advantage of Helpers through the following syntax:
Helpers.DefaultInstance.DoWork();
Swarms of Visual Basic 6 developers have used the Singleton pattern
for years probably without ever realizing it. The Singleton pattern is
behind the default instance of Visual Basic 6 forms, as shown here:
Form1.Show()
The preceding code in Visual Basic 6 invokes the Show method on the default instance of the type Form1.
In the source, there’s no explicit mention of the default instance only
because of the tricks played by the Visual Basic runtime.
Tip
Admittedly, the Singleton pattern on a class is similar to defining
the same class with only static methods. Is there any difference?
With a Singleton pattern, you can actually control the number of
instances because you’re not actually limited to just one instance. In
addition, you can derive a new (meaningful) class because the Singleton
pattern has some instance-level behavior and is not a mere collection of
static functions. Finally, you have more freedom to control the
creation of the actual instance. For example, you can add a static
method, say, GetInstance, instead of the static field and add there any logic for the factory.
Another
interesting pattern to briefly mention is the Strategy pattern. The
pattern identifies a particular functionality that a class needs and can
be hot-plugged into the class. The functionality is abstracted to an
interface or a base class, and the Strategy-enabled class uses it
through the abstraction, as shown here:
public class MyService
{
// This is the replaceable strategy
ILogger _logger;
public MyService(ILogger logger)
{
this._logger = logger;
}
public void DoWork()
{
this._logger.Log("Begin method ...");
.
.
.
this._logger.Log("End method ...");
}
}
The Strategy pattern is the canonical example used to illustrate the power of composition. The class MyService
in the example benefits from the services of a logger component, but it
depends only on an abstraction of it. The external logger component can
be changed with ease and without risking breaking changes. Moreover,
you can even change the component (for example, the strategy) on the
fly. Try getting the same flexibility in a scenario where the
implementation of the strategy object is hard-coded in the MyService
class and you have to inherit a new class to change strategy. It’s just
impossible to change strategy in that case without recompilation and
redeployment.
Architectural patterns capture key elements of software architecture
and offer support for making hard-to-change decisions about the
structure of the system. Software architecture is mostly about decisions regarding design points
that, unlike code design, are not subject to refactoring.
Architectural patterns are selected and applied very early in the
course of design, and they influence various quality characteristics of
the system, such as performance, security, maintenance, and
extensibility.
Examples of architectural patterns are Layers and SOA for modeling
the application structure, Model-View-Controller for the presentation,
Domain Model and Service Layer for the business logic, and Peer-to-Peer
for the network topology.