GOF Behavioural Patterns with C#
By Barry Mossman from Primos.com.au
The source code for this article's demonstration program is also
available from this site. See the link at the end 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 third article about the GOF Design patterns. The first
two articles studied the Creational and Structural Patterns, while
this one looks at the Behavioural 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 end 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
|
|
Chain of Responsibility
|
Allows us to decouple a client's request for action, from the
class that will implement it.
The client builds a chain of candidate classes (Handlers) to
handle the request, and then passes the request to the chain. The
client is simplified as it does not need to know which class will
finally handle the request. The request is passed down the chain,
one handler at a time, until one of them accepts responsibility
for the request.
There is much flexibility as the chain is built at runtime,
and the sequence can therefore be tailored by runtime or
configuration conditions.
|
|
Command
|
Allows us to make commands, or requests against an object,
into objects themselves.
Potential OO benefits include grouping of command series into
macros, providing undo support, facilitating command logging,
persisting commands or macros via Save|Restore, queuing commands
for later execution, and runtime tailoring of commands that are
to be executed.
|
|
Interpreter
|
This pattern enables us to design a script language, it's
syntax, and then implement an Interpreter to process requests
that have been recorded in that language.
This allows us to create a flexible client that is capable of
receiving and actioning high level request scripts written in a
command language that we have designed to suit the users of our
system.
|
|
Iterator
|
Outsource, into a separate class, the task of iteration
through an aggregate's members.
Allows generic navigation code able to address different
aggregate classes. Client can open simultaneous independent
cursors into a single aggregate instance, or an aggregate class
can offer alternate methods of navigation without cluttering it's
implementation or public interface.
An "internal" iterator can be created, allowing the
client to ask the aggregate to step through itself applying a
passed invocation list of methods against each of the aggregate's
members.
|
|
Mediator
|
Collect the rules of interaction between a number of classes
(Colleagues), and encapsulate them into a separate Mediator
class.
Clarify our application's big picture since the cooperation of
the Colleague classes is collected together into the one place.
Can facilitate more reuse, and reduce the disruption caused by
future enhancement. For example a new class of Mediator reusing
Colleagues working together in a new way, or new Colleagues which
could be coordinated in a familiar fashion by an old Mediator
class.
|
|
Observer
|
This pattern allows a number of Observer classes to observe,
and synchronize themselves to, the state of a Subject class.
Allows cohesion without tight coupling. Enables independent
reuse, or enhancement of, Subjects or Observers.
Introduces flexibility as Observers can be added or removed at
runtime, or subsequent versions could introduce new Observer
classes with minimal disruption.
|
|
Momento
|
This pattern allows us to save, or restore, an object's
internal state without violating it's encapsulation.
Addresses security considerations regarding information held
within the Momento, also may be deployed such that only the
object that created the Momento can use it for restoration..
|
|
State
|
This pattern is applicable for objects that need to be able to
change between various states, and where the object needs to have
differing behaviours for the different states.
|
|
Strategy
|
This pattern allows us to encapsulate algorithms as classes in
order to make them reusable, interchangeable or just hidden from
the client.
Use of this pattern can help achieve the goal of having an
application comprised of loosely coupled interchangeable parts,
avoiding an overly monolithic implementation of our application.
The pattern can help to reduce the amount of sub-classing
required in some circumstances.
|
|
Template
|
The Template pattern permits us to offer flexibility and
achieve reuse within application algorithms, while still being
able to retain control over those elements of the algorithm which
we want to run unchanged.
The pattern is related to the Strategy pattern. Where the
Strategy pattern used delegation to vary the whole algorithm, the
Template pattern allows inheritance to specify variation to
portions of a flexible base algorithm
|
|
Visitor
|
In this pattern we design a class, or group of classes, to be
independent of the operations that will be performed upon it. The
operations are collected together into a separate Visitor class,
and the target classes (Elements) are designed to be "visitor
ready”.
Use of this pattern can reduce the impact of future system
changes, and can aid with source code organisation by having
common types of Operation grouped together within the Visitor
class.
|
|