So far I have presented three different approaches to solving the expression
problem, procedural, pure object-oriented, and a visitor pattern. This time I
will present a technique I will refer to as layers. Like the visitor pattern, it is a modification of the
pure object-oriented approach that takes advantage of partial classes. The
solution begins very similarly to the other object-oriented approaches, I create an abstract
class to represent an abstraction of expressions,
abstractpartialclass Expr { }
Notice the addition of the partial modifier. I will
take advantage of that shortly. For now I will just flush out the rest of the
hierarchy in a way that should, by now, be very familiar. I created a class to
represent literals,
partialclass Literal: Expr {
protecteddouble Value;
public Literal(double value) { Value = value; }
}
Notice that, as with the pure object-oriented approach, I can keep the field
used to hold the value as protected. This is also true for the class to
represent an abstraction of binary operators.
abstractpartialclass BinaryOperator: Expr {
protected Expr Left;
protected Expr Right;
public BinaryOperator(Expr left, Expr right) {
Left = left;
Right = right;
}
}
The actual binary operators are just descendants of
BinaryOperator with no additional data.
So far, this is nearly identical to the pure object-oriented approach. Where
it differs is in how new operations are added. To add the
Evaluate() method, instead of modifying each class to add the methods, I can take advantage of the fact I left them
as partial classes and create additional parts that contain the methods instead.
First I created a new part of the Expr class to
introduce the virtual method Evaluate().
Compiling now will generate several errors indicating that the other classes
do not implement the Evaluate() method. This is
want I wanted. Using abstract in the above class part lets the compiler help me
determine when I have completed the implementation of the evaluate operation.
The rest of the class parts for Evaluate() looks like,
I call each of the collections of class parts a layer. You can think
of them much like a transparency teachers uses on overhead projectors. You can
build up a class by combining layers of functionality. I usually keep each layer
in its own file named for the layer. In this case, Evaluator.cs might be a good
choice.
To demonstrate building up a class with through layers I
will create a printing layer.
As in the other approaches, I will demonstrate how to add support for the
Power expression form. I have two choices, I can add it just as we did in the pure object-oriented approach,
The Expression Problem: Part IV - Layers
So far I have presented three different approaches to solving the expression problem, procedural, pure object-oriented, and a visitor pattern. This time I will present a technique I will refer to as layers. Like the visitor pattern, it is a modification of the pure object-oriented approach that takes advantage of partial classes. The solution begins very similarly to the other object-oriented approaches, I create an abstract class to represent an abstraction of expressions,
abstract partial class Expr { }Notice the addition of the partial modifier. I will take advantage of that shortly. For now I will just flush out the rest of the hierarchy in a way that should, by now, be very familiar. I created a class to represent literals,
partial class Literal: Expr { protected double Value; public Literal(double value) { Value = value; } }Notice that, as with the pure object-oriented approach, I can keep the field used to hold the value as protected. This is also true for the class to represent an abstraction of binary operators.
abstract partial class BinaryOperator: Expr { protected Expr Left; protected Expr Right; public BinaryOperator(Expr left, Expr right) { Left = left; Right = right; } }The actual binary operators are just descendants of BinaryOperator with no additional data.
partial class Add: BinaryOperator { public Add(Expr left, Expr right) : base(left, right) { } } partial class Subtract: BinaryOperator { public Subtract(Expr left, Expr right) : base(left, right) { } } partial class Multiply: BinaryOperator { public Multiply(Expr left, Expr right) : base(left, right) { } } partial class Divide: BinaryOperator { public Divide(Expr left, Expr right) : base(left, right) { } }So far, this is nearly identical to the pure object-oriented approach. Where it differs is in how new operations are added. To add the Evaluate() method, instead of modifying each class to add the methods, I can take advantage of the fact I left them as partial classes and create additional parts that contain the methods instead. First I created a new part of the Expr class to introduce the virtual method Evaluate().
abstract partial class Expr { public abstract double Evaluate(); }Compiling now will generate several errors indicating that the other classes do not implement the Evaluate() method. This is want I wanted. Using abstract in the above class part lets the compiler help me determine when I have completed the implementation of the evaluate operation. The rest of the class parts for Evaluate() looks like,
partial class Literal { public override double Evaluate() { return Value; } } abstract partial class BinaryOperator { public sealed override double Evaluate() { return EvaluateOp(Left.Evaluate(), Right.Evaluate()); } protected abstract double EvaluateOp(double left, double right); } partial class Add { protected override double EvaluateOp(double left, double right) { return left + right; } } partial class Subtract { protected override double EvaluateOp(double left, double right) { return left - right; } } partial class Multiply { protected override double EvaluateOp(double left, double right) { return left * right; } } partial class Divide { protected override double EvaluateOp(double left, double right) { return left / right; } }I call each of the collections of class parts a layer. You can think of them much like a transparency teachers uses on overhead projectors. You can build up a class by combining layers of functionality. I usually keep each layer in its own file named for the layer. In this case, Evaluator.cs might be a good choice.
To demonstrate building up a class with through layers I will create a printing layer.
abstract partial class Expr { public abstract void Print(); } partial class Literal { public override void Print() { Console.Write(Value); } } abstract partial class BinaryOperator { public sealed override void Print() { Left.Print(); PrintOp(); Right.Print(); } protected abstract void PrintOp(); } partial class Add { protected override void PrintOp() { Console.Write(" + "); } } partial class Subtract { protected override void PrintOp() { Console.Write(" - "); } } partial class Multiply { protected override void PrintOp() { Console.Write(" * "); } } partial class Divide { protected override void PrintOp() { Console.Write(" / "); } }As in the other approaches, I will demonstrate how to add support for the Power expression form. I have two choices, I can add it just as we did in the pure object-oriented approach,
class Power: BinaryExpr { public Power(Expr left, Expr right) : base(left, right) { } protected override double EvaluateOp(double left, double right) { return Math.Pow(left, right); } protected override void PrintOp() { Console.Write(" ^ "); } }or I could divide it into multiple parts that can be co-located with the other similar parts. The data part would look like,
partial class Power: BinaryExpr { public Power(Expr left, Expr right) : base(left, right) { } }The part for the expression layer would look like,
partial class Power: BinaryExpr { protected override double EvaluateOp(double left, double right) { return Math.Pow(left, right); } }An the printing layer part would look like,
partial class Power: BinaryExpr { protected override void PrintOp() { Console.Write(" ^ "); } }It is this flexibility that makes layers so appealing. You can decide, case by case, which is the right way to modify the hierarchy.
5:48 PM | Comments [1] | #Programming
12/03/2005 8:38 PM
Great stuff!Damn wish I had known or figured this approach week ago :-)
ac | mailto:acAT NOSPAMac dot ca | acAT NOSPAMac dot ca