Runtime flexibility and future proofing; GOF Behavioural Patterns
with C# - part 1
BarryMossman<is_at>primos.com.au
This is my third article about the GOF Design patterns. The first
two articles studied the Creational and Structural Patterns, while
this one begins to look at the Behavioural Patterns. There are links
to my first two articles at the bottom of this one.
There is too much material within the Behavioural patterns to be
covered by one article, so I cover just the first three patterns in
this article, and will mop up the remainders in a follow
ups. This articles covers the following patterns:
The following Behavioural patterns have been deferred
to follow up articles:
Iterator, Mediator, Observer, Memento,
State, Strategy, Template Method, Visitor
The source for this article's demonstration
program is available at CodeCentral. See the link
at the bottom of this document.
The first article (Creational Patterns) also gave an overview of
where the patterns came from, and the general discussion of the
general techniques that they promoted. I will firstly briefly recap a
few of the points from that article.
The GOF design patterns help address the following challenges :
design ready to accommodate change & growth
design flexible systems that 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. There is a link to this
book at the end of this document.
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 way offer a great deal of runtime flexibility, and
are better set up for future modification. A general theme of the
Behavioural 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 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. |
Chain of Responsibility
Pattern
This pattern allows us to decouple a client's request for action,
from the class that will implement it. We have seen something similar
in the Bridge pattern (Structural
patterns). In the case of the Bridge pattern the client decided
at runtime which Implementor class was going to handle calls to the
Adapter's interface. This gave us runtime flexibility, but the client
needed to be involved in choosing which Implementor class was best to
handle the request.
In the Chain of Responsibility pattern the client sets up a chain
of candidate Implementors (called Handlers in this pattern). The
request is passed to the chain, where it bubbles up, until one of the
Handlers determines that it should handle the request itself. The
client is simplified as it does not need to know which class will
finally handle the request. It just passes the request to the object
that implements the chain pattern, and leaves the chain to sort out
who should handle things.
A familiar example of this kind of design is when an Exception is
raised in the client. The exception bubbles up the invocation stack
until somebody handles it. The client doesn't need to worry about who
should handle the exception. It can trust that somebody appropriate
will handle it, even if it is just the default handler in the dotNet
framework.
The design is flexible as the chain is built, and can be
re-configured, at runtime.
The Handlers are relatively simple as they don't need to know
about the client, nor do they need to know about other handlers in
the chain other than their immediate successor. However the chain
does need to be set up correctly to ensure that some Handler will
pick up responsibility for the request, and that it won't just fall
off the end of the chain.
The chain could use existing relationships between the Handlers if
such relationships exist(eg. Parent references if there were nodes
with a Composite pattern (see Structural
patterns), or the client can define it's own set of relationships
between the handlers in the chain.
Our demonstration program simulates a dynamic requirement by
passing the current time into the chain of handlers. The handler that
will accept responsibility is dependant how far through the current
minute we have progressed.
Firstly our client code is as follows:
/* Set up a chain of candidate handlers to handle the request. Chain
will be (1st considered -> last): ConcreteHandler1,
ConcreteHandler2, ConcreteHandler3. */
ShowUserCommentary(1);
HandlerBase chain = new ConcreteHandler3();
HandlerBase more = new ConcreteHandler2();
more.Successor = chain;
chain = new ConcreteHandler1 ();
chain.Successor = more;
// hand the request to the chain
listBox1.AppendText(chain.SayWhen(DateTime.Now));
This produces the following output (as with all of the following
examples the black text is output by the ShowUserCommentary method,
and the blue text is output by the pattern).:

Now to the implementation, the essential interface for the pattern
could be described as follows:
public interface IHandler {
// properties
HandlerBase Successor { set; }
}
Firstly we need to define an abstract base class for the Handlers.
This is where we implement the Successor property:
// --- Abstract Handler
abstract public class HandlerBase {
// fields
protected HandlerBase _successor;
// properties
public HandlerBase Successor {
set { _successor = value; }
}
// methods
abstract public string SayWhen(DateTime localTime);
}
Finally we have the implementation of the Handlers. Here are the
first two:
// --- Concrete handlers
public class ConcreteHandler1: HandlerBase {
// methods
override public string SayWhen(DateTime localTime) {
if (localTime.Second < 15)
return String.Format(I am {0}.nThe time is {1:T}nWe are just starting on a shiny new minute. Just think of all the things you could do with it!,
this.ToString(),localTime);
else {
if (_successor != null) {
return _successor.SayWhen(localTime);
}
else
throw new ApplicationException(ChainOfResponsibility object exhaused all successors without call being handled.);
}
}
}
public class ConcreteHandler2: HandlerBase {
// methods
override public string SayWhen(DateTime localTime) {
if ((localTime.Second >= 15) & (localTime.Second < 45))
return String.Format(I am {0}.nThe time is {1:T}nWe are into the middle half of this minute. You need to get started if you going to use it well.,
this.ToString(),localTime);
else {
if (_successor != null) {
return _successor.SayWhen(localTime);
}
else
throw new ApplicationException(ChainOfResponsibility object exhaused all successors without call being handled.);
}
}
}
Command Pattern
The Command pattern allows us to make a request into an object.
The target of the request is called the Receiver, and it knows how to
carry out the steps involved in servicing the request. The client
wants to have the request serviced, but need not know the steps
involved, nor have any knowledge of the Receiver's interface. The
client creates a Command object which contains the request type, any
parameters, and a reference to the target Receiver. This Command
object contains the detailed knowledge of the steps involved in
servicing the client's request, and has knowledge of the Receiver's
interface.
The pattern can also have an optional object called the Invoker
which instructs the command to execute it's request. This could be a
menu item or some other GUI control.
Macro Commands can be created which contain a collection of other
Command objects.
Possible benefits from use of the command pattern include:
decouple the client from the request as well as the object
that will be handling it.
ability to queue up requests for action at a later time
ability to log the requests away for possible re-application
after returning the system to a backup check point following a crash
ability to bundle up a series of plodding detailed requests
into a larger transaction (macro) that makes the business intention
clearer
provide undo support
flexibility where we can provide some runtime configuration
to the commands that are issued
allow transfer of the command to a different process for
handling
The client will be simplified if it can request simple chunky
commands that relate to business transactions. The system design
intent can be more easily seen if the plodding internal detail for
each transaction type is outsourced to the Command objects. It is
also reasonably straight forward to add further business transaction
types by creating new Command classes. This will involve minimal
change requirement inside the client.
My demonstration program shows the following:
an individual command is executed
then a macro command set, with undo support, is executed and
undone
the macro command set is persisted to disk
the macro command set is reloaded, and applied to a different
Receiver.
Here is the client:
// Create a Receiver, then a Command to add 100.00 to it.
// Execute the command, and show the result.
ShowUserCommentary(1);
Receiver receiver = new Receiver();
// note: the m suffix causes a literal of type Decimal
CommandBase command = new CreditCommand(100m,receiver);
command.Execute();
listBox1.AppendText(String.Format(nValue is {0},receiver.Value));
// Create a 2nd receiver. Then create a macro Command set that adds
// 50.00 to the 2nd receiver, and then subtracts 20.00 from the 1st
// and then increases the 1st by 5%. Execute the macro.
ShowUserCommentary(2);
Receiver receiver2 = new Receiver();
MacroCommand macro = new MacroCommand();
command = new CreditCommand(50m,receiver2);
macro.Add(command);
command = new DebitCommand(20m,receiver);
macro.Add(command);
command = new PercentIncreaseCommand(5m,receiver);
macro.Add(command);
macro.Execute();
listBox1.AppendText(String.Format(
nValue 1st receiver is now {0}, and 2nd receiver is {1}
,receiver.Value, receiver2.Value));
// Undo the effects of the above macro.
ShowUserCommentary(3);
macro.Undo();
listBox1.AppendText(String.Format(
nValue 1st receiver is now {0}, and 2nd receiver is {1}
,receiver.Value, receiver2.Value));
// Persist the macro command to disk.
macro.Save(MacroSave.bin);
// Reload the macro, and then execute all of the commands against the
// second Receiver.
ShowUserCommentary(4);
MacroCommand macro2 = macro.Load(MacroSave.bin, receiver2);
macro2.Execute();
listBox1.AppendText(String.Format(
nValue 1st receiver is now {0}, and 2nd receiver is {1}
,receiver.Value, receiver2.Value));

The essential public interface for the pattern could be described
as:
public interface ICommand {
// methods
void Execute();
}
Here is the abstract ancestor for the Commands and Macro class.
You may notice the Serializable attribute which I
will discuss when showing the implementation for the Macro Command at
the end of this section
.
// --- Abstract Command
[Serializable]
public abstract class CommandBase {
// fields
internal Receiver _receiver;
internal decimal _amount;
//properties
public virtual Receiver TargetReceiver {
set {_receiver = value;}
}
// constructor
public CommandBase (decimal aAmount, Receiver aReceiver) {
_receiver = aReceiver;
_amount = aAmount;
}
protected CommandBase () { // only by the MacroBase descendant
}
// methods
abstract public void Execute();
abstract public void Undo();
}
Next comes the concrete implementation of the credit
command. There are similar implementations for the debit
and percentage increase commands which have been
omitted for brevity. The Command class provides an interface, but
doesn't actually know the details of how to achieve the request's
intentions. It is however familiar with the Receiver, and knows which
Receiver members to employ to achieve the desired result.
// --- Concrete Commands
[Serializable]
public class CreditCommand: CommandBase {
// constructor
public CreditCommand(decimal aAmount, Receiver aReceiver):
base (aAmount, aReceiver) {}
// methods
override public void Execute() {
_receiver.UpdateValue(ReceiverAction.add,_amount);
}
override public void Undo() {
_receiver.UpdateValue(ReceiverAction.subtract,_amount);
}
}
Then we should look at the Receiver. This is the class that knows the
mechanics of how to action the request.
// --- Receiver
public enum ReceiverAction {add,subtract,percentIncrease,percentDecrease}
[Serializable]
public class Receiver {
// fields
decimal _value;
// properties
public decimal Value {
get { return _value; }
}
// methods
internal void UpdateValue (ReceiverAction aOperation,decimal aAmount) {
switch (aOperation) {
case ReceiverAction.add:
_value += aAmount;
break;
case ReceiverAction.subtract:
_value -= aAmount;
break;
case ReceiverAction.percentIncrease:
_value *= (1+aAmount/100);
break;
case ReceiverAction.percentDecrease:
_value /= (1+aAmount/100);
break;
default:
throw new ApplicationException(Invalid operation);
}
}
}
The above pieces are all that we need to enable our client to execute
individual commands and to provide undo support. Now lets us look at
providing macro support and persistence of the commands to disk.
The dotNet framework makes it relatively simple to implement the
persistence, and retrieval, of our commands as it it has extensive
inbuilt support for Serialization. Serialization is where an
in-memory object is flattened out to a form that can be transmitted
or stored. Deserialization is the reverse process.
We are given two flavours of serialization with dotNet; internally
using either XML or a binary format. XML Serialization will handle
our objects public fields and properties, but for this task we need
all the type information, methods, private fields etc so we will use
the binary format option.
All we need to do to make a class serializable, is mark it with
the Serializable attribute. This will cause the type's meta data to
be marked as needing serialization support from the runtime support
(CLR)when our class is instantiated.
To implement the macro facility we subclass from our Abstract
CommandBase, and then implement the interface's Save and Load
functionality as shown below. The TargetReceiver property is used to
bind any loaded (deserialize) commands to their new Receiver. The
Macro functionality is implemented via the Add, Execute and Undo
methods.
[Serializable]
public class MacroCommand: CommandBase {
// fields
ArrayList _commandStack;
//properties
override public Receiver TargetReceiver {
set {
foreach(CommandBase command in _commandStack) {
command.TargetReceiver = value;
}
}
}
// constructor
public MacroCommand(): base() {
_commandStack = new ArrayList();
}
// methods
override public void Execute() {
foreach(CommandBase command in _commandStack) {
command.Execute();
}
}
override public void Undo() {
ArrayList commandsReversed = (ArrayList)_commandStack.Clone();
commandsReversed.Reverse();
foreach(CommandBase command in commandsReversed) {
command.Undo();
}
}
public void Add(CommandBase aCommand) {
_commandStack.Add(aCommand);
}
public void Save(string aFileName) {
IFormatter formatter = new BinaryFormatter();
Stream stream =
new FileStream (aFileName,FileMode.Create,
FileAccess.Write,FileShare.None);
formatter.Serialize(stream,this);
stream.Close();
}
public MacroCommand Load(string aFileName, Receiver aReceiver) {
IFormatter formatter = new BinaryFormatter();
Stream stream =
new FileStream(aFileName,FileMode.Open,
FileAccess.Read, FileShare.Read);
MacroCommand macro = (MacroCommand)formatter.Deserialize(stream);
stream.Close();
macro.TargetReceiver = aReceiver;
return macro;
}
}
You may have noticed that only the MacroCommand class is serialized,
but other classes such as Rec and AbstractCommand have also been
marked with the Serializable attribute. This is because while it is
true that only the MacroCommand is explicitly serialized, the
other class are implicitly serialized. This is because the
MacroCommand contains references to these other types. See for
example:
public MacroCommand Load(string aFileName, Receiver aReceiver) {
foreach(CommandBase command in _commandStack) {
The Concrete methods such as CreditCommand are not explicitly
mentioned, but they need to marked with the serializable attribute
also. If you fail to do so the program will compile successfully, but
you will get a SerializationException at runtime. This will occur
because the actual objects within the macro are of course instances
of these concrete classes, and the CLR will not be able to serialize
or deserialize them unless they are identified as needing
serialization support.
Interpreter Pattern
This pattern allows us to define a command or query language and
it's grammar, to create grammatical sentences in that language, and
then to interpret the sentences at runtime. This allows us to create
a flexible client that is capable of receiving and actioning a
request in a script format written in a command language of our own
invention. The pattern allows much high level client flexibility as
the command script could be obtained at runtime; maybe built or keyed
in by the user, or obtained from a configuration file.
The client receives the command sentence as an Expression syntax
tree which is built from a combination of terminal and non-terminal
nodes to allow nesting within the command sentence's logical
structure. The parsing of a command script to build the syntax tree
is not part of the Interpreter pattern, although my demonstration
program shows an example of this step.
There is an abstract Expression class that is inherited by all
tree nodes. This base class has an abstract Interpret method. There
is a concrete sub-class of this base for each grammatical rule within
the language that we are implementing. The client creates a Context
instance and a syntax tree, and then asks the syntax tree to
Interpret itself passing in the context instance as an argument. The
grammatical composition of the sentence is represented by
the tree, and it's layout causes the various sub-classes to fire in
the correct sequence to action this specific command. The sub-classes
use the context instance parameter as working storage to build and
store the result from the command sentence.
The GOF recommend that the although it is quite easy to extend or
modify our language's grammar the overall gramme should not be
allowed to get too complex.
My demonstration program contains two illustrations:
the first implements a simple language that allows the client
to build a flexible date based string that would be suitable for use
as a archive backup file name
the second example allows us to write a query command to
interrogate a date known to the context
The first illustration allows the client to build a name for a
archive file that we may like to write. The file name is to be
assembled from some combination of day, month & year digits, our
pattern's namespace name, and a string that we supply at runtime. The
pattern will build the string as instructed by the shape of the
syntax tree received from the client.
Firstly let us look at client code that will explicitly build and
use this syntax tree, using a file name structure that is known at
compile time. In this case the file name will be built in the
following sequence:
ShowUserCommentary(1);
ArrayList commandList = new ArrayList();
commandList.Add(new ExpressionCodeYear());
commandList.Add(new ExpressionCodeMonth());
commandList.Add(new ExpressionCodeDay());
commandList.Add(new ExpressionCodeConstantString(-));
commandList.Add(new ExpressionCodeNamespace());
Context context = new Context();
foreach(ExpressionBase exp in commandList)
exp.Interpret(context);
/* display the file name assembled by the Interpreter from our syntax
tree */
listBox1.AppendText(context.FileName);
This pattern is more powerful if the syntax tree structure is not
determined until runtime, after parsing a command script, which could
have been obtained from the user, or obtained from a configuration
file. In this next example the client parses the string n%-%ymd
which will cause the following syntax tree to be built:
/* Now parse a command string to determine how to build the syntax
tree. This allows flexibility as the command string may have been
supplied at runtime */
ShowUserCommentary(2);
InterpreterParsing parse = new InterpreterParsing();
string fileNamePattern = n%-%ymd;
commandList = parse.ParseFileNamePattern(fileNamePattern);
context = new Context();
foreach(ExpressionBase exp in commandList)
exp.Interpret(context);
listBox1.AppendText(context.FileName);
When we run the test the following output is produced:

The essential interface for the pattern could be described as:
public interface IContext { }
public interface IExpression {
// methods
void Interpret(IContext aContext);
}
I will ignore the parsing step for the moment as this is not really
part of the Interpreter pattern. Let us imagine that the Expression
tree object has already been built as in the first client snippet,
and look at the implementation of the pattern classes that will
interpret this tree. The first part of the implementation that we
shall look at is the Context which contains working storage used
during evaluation of the expression, and also contains the result
returned from this evaluation:
// --- Context
public class Context {
// fields
internal string _fileName = ;
// properties
public string FileName {
get { return _fileName; }
}
}
Then there is the definition of a base class for the Expression
nodes:
// --- Abstract Expression
public abstract class ExpressionBase {
// methods
public abstract void Interpret(Context aContext);
}
And then the concrete Expression classes each of which provides
implementation of the abstract Interpret method. I have omitted the
Month and Year classes for brevity as they are only slightly
different from the day class which is shown:
// --- Concrete Expressions
public class ExpressionCodeDay: ExpressionBase {
// methods
public override void Interpret(Context aContext) {
aContext._fileName += String.Format({0:dd},DateTime.Today);
}
}
public class ExpressionCodeNamespace: ExpressionBase {
// methods
public override void Interpret(Context aContext) {
aContext._fileName += String.Format({0},this.GetType().Namespace);
}
}
public class ExpressionCodeConstantString: ExpressionBase {
// fields
string _string;
// constructors
public ExpressionCodeConstantString(string AString) {
_string = AString;
}
// methods
public override void Interpret(Context aContext) {
aContext._fileName += String.Format({0},_string);
}
}
Parsing of a command script to build the syntax tree is not actually
part of the pattern, but my demonstration program includes this step
as this is what makes the pattern powerful. Here is the code that
does the parsing. This kind of problem is made quite simple if you
employ Regular Expressions engine implemented into dotNet to break
apart the string being parsed.
/*---------------------------------------------------
Parse a file name expression string and build an Interpreter pattern
command structure as directed. Expression syntax is some combination
chosen from the following:
y = year
m = month
d = day
n = namespace name
%s% where s is a string (no embedded blanks)
So an expression string of ymd%--%n would produce the following
Interpreter syntax tree:
ArrayList commandList = new ArrayList();
commandList.Add(new ExpressionCodeYear());
commandList.Add(new ExpressionCodeMonth());
commandList.Add(new ExpressionCodeDay());
commandList.Add(new ExpressionCodeConstantString(--));
commandList.Add(new ExpressionCodeNamespace());
---------------------------------------------------*/
public ArrayList ParseFileNamePattern(string aPattern) {
ArrayList commandList = new ArrayList();
Regex regex = new Regex(
@%.* # a constant string identified by a leading %
(?=%) # drop off the trailing % so it is discarded
|y # y = year
|m # m = month
|d # d = day
|n # n = namespace,
RegexOptions.IgnoreCase
| RegexOptions.Multiline
| RegexOptions.IgnorePatternWhitespace
| RegexOptions.Compiled
);
MatchCollection itemList = regex.Matches(aPattern);
foreach (Match m in itemList) {
switch (m.ToString().ToLower()[0]) {
case '%':
if (m.Length > 1) {
string stringText =
m.ToString().Substring(1,m.Length-1);
commandList.Add(new
ExpressionCodeConstantString(stringText));
}
break;
case 'y':
commandList.Add(new ExpressionCodeYear());
break;
case 'm':
commandList.Add(new ExpressionCodeMonth());
break;
case 'd':
commandList.Add(new ExpressionCodeDay());
break;
case 'n':
commandList.Add(new ExpressionCodeNamespace());
break;
}
}
return commandList;
}
Interpreter Pattern – second illustration.
My second illustration of the Interpreter pattern is to write a
query command to interrogate a date known to the context. In this
example we implement a query language that would allow the user to
write a query string that would interrogate a date and return a
boolean result. This second example has been included to demonstrate
the use of non-terminal nodes in the query expression which allows
logical nesting of sub queries within the query expression.
A comment block follows that explains the syntax of the language
that we will implement. I won't include the parsing step this time as
it is incidental to the Interpreter pattern as proscribed by the GOF,
and is complex enough to be a diversion. The source is available in
my demonstration program if you are
interested.
/*---------------------------------------------------------------
Parse a BORCON date query expression string, and build an Interpreter
pattern command structure as directed. The expression will allow the
user to write a query which will return a true|false result. In this
example they are querying the date of the 2004 USA BORCON. The query
syntax uses the following elements to build sub queries:
identifier: y = year
m = month
d = day
operator : <,>,=
value : digits (year is in form yyyy, eg. 2004, not 04)
Sub queries may be ANDed or ORed against each other.
Examples of valid queries are:
y=2004
(y=2004)
(y=2004)&(m=12)
((y=2004)&(m=12))|(y<2004) etc (any depth is allowed)
The last example above would produce the following Interpreter syntax tree:
-+->new ConcreteQueryNonTerminalORExpression()
+--+--> new ConcreteQueryNonTerminalANDExpression()
+-----> new QueryTerminalYear('=',2004)
+-----> new QueryTerminalMonth('=',12)
+-----> new QueryTerminalYear('<',2004)
--------------------------------------------------------------------*/
Here is the client call to initiate the demonstration:
/* This 2nd illustration of the Interpreter pattern allows the
client to interrogate a date known to the Context instance (it is
the date of the 2004 USA Borcon). The result of the interpretation
will be a boolean. The gramme allows the +, > and < operators.
The year, month and day can be tested. Sub-queries can be ANDed
or ORed against each other to any level of nesting. The syntax
tree is built at runtime by parsing a command string. */
ShowUserCommentary(3);
string script = ((y=2004)&(d>10))&((m=10)|(m=9));
QueryExpressionBase query = parse.ParseQuery(script, testOutput);
Context_Query queryContext = new Context_Query();
query.Interpret(queryContext);
listBox1.AppendText(String.Format(nnThe query evaluates as {0},
queryContext.value.ToString()));
To implement this illustration of the Interpreter pattern we firstly
we need to define the Context:
// --- Context
public class Context_Query {
// fields
internal bool _value;
internal readonly DateTime _Borcon2004Start = new DateTime(2004,9,11);
// properties
public bool value {
get { return _value; }
}
}
Then there is the base class that is shared by both terminal and
non-terminal Expression nodes:
public abstract class QueryExpressionBase {
// methods
public abstract void Interpret(Context_Query aContext);
}
Now the implementation of the syntax tree. This time the tree is
composed from terminal and non-terminal nodes. In the terminal nodes
we evaluate a sub-query which have uses a =, <
or > operator. In the non-terminal node we and
or or the results of two sub-queries.
((y=2004)&(m=12))|(y<2004) etc (any depth is allowed)
(y=2004) and (m=12) are examples of sub queries.
Firstly let's look at the terminal nodes. The following shows their
abstract ancestor, and the implementation of the concrete class which
handles the year comparisons. There are similar class which handle
month and day comparisons which have been omitted for brevity.
public abstract class QueryTerminalBase: QueryExpressionBase {
// fields
protected char _operation;
protected int _ComparisonValue1, _ComparisonValue2;
// methods
override public void Interpret(Context_Query aContext) {
switch (_operation) {
case '=':
aContext._value =
(_ComparisonValue1 == _ComparisonValue2);
break;
case '>':
aContext._value =
(_ComparisonValue1 > _ComparisonValue2);
break;
case '<':
aContext._value =
(_ComparisonValue1 < _ComparisonValue2);
break;
default:
throw new ApplicationException(String.Format(Unexpected operation; operation was {0}. Only =, < or > allowed.,
_operation));
}
}
}
public class QueryTerminalYear: QueryTerminalBase {
// constructors
public QueryTerminalYear(char aOperation, int aValue) {
_operation = aOperation;
_ComparisonValue2 = aValue;
}
// methods
override public void Interpret(Context_Query aContext) {
_ComparisonValue1 = aContext._Borcon2004Start.Year;
base.Interpret(aContext);
}
}
Finally we need to look at the non-terminal classes which handle the
and and or operations. Recursion handles
the situation where were there are nested sub-queries.
abstract public class QueryNonTerminalExpressionBase: QueryExpressionBase {
// fields
private ArrayList _childContents = new ArrayList();
protected bool _leftSide = false, _rightSide = false;
protected bool _firstSide = true;
// methods
override public void Interpret(Context_Query aContext) {
_firstSide = true;
foreach (QueryExpressionBase xx in _childContents) {
xx.Interpret(aContext);
if (_firstSide)
_leftSide = aContext._value;
else
_rightSide = aContext._value;
_firstSide = false;
}
}
public void Add(QueryExpressionBase aExpression) {
_childContents.Add(aExpression);
}
}
public class ConcreteQueryNonTerminalANDExpression:
QueryNonTerminalExpressionBase {
// methods
override public void Interpret(Context_Query aContext) {
base.Interpret(aContext);
aContext._value = _leftSide & _rightSide;
}
}
public class ConcreteQueryNonTerminalORExpression:
QueryNonTerminalExpressionBase {
// methods
override public void Interpret(Context_Query aContext) {
base.Interpret(aContext);
aContext._value = _leftSide | _rightSide;
}
}
As noted above, I have omitted the parsing step from this article as
it is not really part of the Interpreter pattern. The source code is
available in the demonstration program
Conclusion
This article has begun examination of the GOF's Behavioural
Patterns from those described in their book titled Design
patterns. I think that the study of the design patterns is a
worthwhile thing to do. I found the following links useful while
studying the patterns myself and while preparing this article. The
book itself is a good investment as it provides supplementary detail
upon the problems that the patterns are trying to solve, the elements
of the solution, and the consequences and trade-off's involved in
using the patterns.
Look out for my closing article in this series will will study the
remaining Behavioural Patterns.
Links