GOF Structural Design Patterns with C#
by BarryMossman from Primos.com.au
The source code for this article's demonstration program is also
available from this site. See the link at the
bottom of this document. The program contains annotated example
displays, and also displays some notes about each of the patterns, so
if you are interested in starting to work with the patterns it may be
a useful utility to have on your desktop during the learning period.
This is my second article about the GOF Design patterns. The first
article studied the Creational Patterns, while this one looks at the
Structural Patterns. The first article gave an overview of where the
patterns came from, and a general discussion about the general
techniques that they promoted. There is a link
to that article at the bottom of this one, but I will firstly briefly
recap a few of the points made there.
The GOF design patterns help address the following challenges :
design ready to accommodate change
& growth
design flexible systems which come
ready to handle reconfiguration and run time tailoring
code in manner to facilitate reuse
during the development and extension phases ... ie. both external
and internal reuse, so that we are rewarded by efficiencies as the
project progresses, coming for investments made earlier in the
project.
implement change in a way that doesn't overly shorten the
system's useful lifespan
In a multi-person project the design patterns have the additional
utility of providing a shorthand language with which to describe
design options and specifications.
The design patterns were defined in the programming classic
entitled "Design Patterns" by Gamma, Helm, Johnson &
Vlissides. The four authors are commonly described as the Gang Of
Four (GOF) for brevity. The subtitle of the book is "elements of
reusable object-oriented software".
The GOF described various categories of patterns:
One of the book's key points is that the authors favour using
"object composition" over "class inheritance"
when designing our system's objects. This means that our objects are
assembled at runtime from a number of helper classes working together
to deliver the desired behaviour, rather than being statically
defined at compile time using class inheritance. Objects that are
created in this favoured way offer a great deal of runtime
flexibility, and are better set up for future modification. A general
theme of the design patterns is that they allow the composition of
larger and more flexible structures from smaller helper classes.
The approach necessitates more physical classes, but is made more
workable where the patterns are used as they help to bring an
understandable structure to the design. The fact that the patterns
are based upon system design structures that have been tested and
refined over time is also of assistance.
Note that the advice is to only to "favour"
object composition over class inheritance, not to stop using
inheritance altogether. The two techniques work well together.
A summary of the patterns
|
Pattern Name
|
General Objective
|
|
Adapter
|
Allows us to provide a new interface for a class that already
exists, allowing reuse of a heritage class where our client
requires a different interface.
Also allows us to build a new class which will have
"pluggable" adapters tailored for individual client
needs.
|
|
Bridge
|
Allows us to decouple a class from it's interface.
This allows the class and it's interface to be changed
independently over time which can lead to more reuse and less
future shock.
It also allows us to dynamically switch between
implementations at runtime allowing increased levels of runtime
flexibility.
|
|
Composite
|
A flexible pattern that allows the client to deal with complex
and flexible tree structures.
The trees can be built from various types of containers or
leaf nodes, and it's depth or composition can be adjusted or
determined at runtime.
The client is simplified as it can deal with the tree as a
single object, as the Composite pattern can take care of dealing
appropriately with all the differing component parts.
|
|
Decorator
|
Allows us to dynamically modify an object at runtime by
attaching new behaviours, or by modifying existing ones.
Is applied just to specific object instances rather than to
the class as a whole.
Can allow design of "pay as you go" systems where
overhead is incurred only when runtime, or configuration options,
require it.
Is best limited to changing an object's "skin"
rather than it's "guts".
|
|
Facade
|
This pattern allows us to simplify the client by creating a
simplified interface into a subsystem, or by creating a unified
interface into a group of subsystems.
Potential benefits are to simplify the client,
compartmentalise the client, help future proof our application,
or enable more reuse.
|
|
Flyweight
|
This pattern optimises memory use when we need a design a
class with which our client will need to create a very large
number runtime of objects.
The pattern is most applicable if there will be clusters of
runtime objects that have similar state (data), as it arranges
sharing of these instances.
The pattern works well with the Composite pattern.
|
|
Proxy
|
This pattern provides a surrogate object that controls access
to some other object.
The aim is to simplify the client when it needs to use an
object that has complications.
Examples include objects upon a remote system, objects who
have client authentication requirements, or objects that are
expensive to fully create so some client purposes may be served
with just a cut down instantiation.
|
|