03 December 2005

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 flesh 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.



blog comments powered by Disqus