This is perfect for OO languages as the way references work. At an abstract level I would handle this as such:
Create an abstract class, Expression
, that will be the base type for all values in our program. Something like:
public abstract class Expression
{
List<Expression> linkedExpressions;
protected Expression lhs; // left hand side, right hand side
protected Expression rhs;
protected Expression(Expression x, Expression y)
{
List<Expression> linkedExpressions = new List<Expression>();
lhs = x;
rhs = y;
// let the expressions know that they have a expression dependant on them
lhs.NotifyExpressionLinked(this);
rhs.NotifyExpressionLinked(this);
}
private void NotifyExpressionLinked(Expression e)
{
if (e != null)
{
linkedExpressions.Add(e);
}
}
private void NotifyExpressionUnlinked(Expression e)
{
if (linkedExpressions.Contains(e)
{
linkedExpressions.Remove(e);
}
}
// this method will notify all subscribed expressions that
// one of the values they are dependant on has changed
private void NotifyExpressionChanged()
{
if (linkedExpressions.Count != 0) // if we're not a leaf node
{
foreach (Expression e in linkedExpressions)
{
e.NotifyExpressionChanged();
}
}
else Evaluate()
// once we're at a point where there are no dependant expressions
// to notify we can start evaluating
}
// if we only want to update the lhs, y will be null, and vice versa
public sealed void UpdateValues(Expression x, Expression y)
{
if (x != null)
{
lhs.NotifyExpressionUnlinked(this);
x.NotifyExpressionLinked(this);
lhs = x;
}
if (y != null)
{
rhs.NotifyExpressionUnlinked(this);
y.NotifyExpressionLinked(this);
rhs = y;
}
NotifyExpressionChanged();
}
public virtual float Evaluate()
{
throw new NotImplementedException(); // we expect child classes to implement this
}
}
Create a class for each of the types of expressions we will need. At the very bottom you will have a LiteralExpression
, which is just a number:
public class LiteralExpression : Expression
{
private float value;
public LiteralExpression(float x)
: base(null, null) { } // should not have any linked expressions
public override float Evaluate()
{
return value;
}
}
One thing to note about this class - because of the way it works, UpdateValue() shouldn't be used on it. Instead, just create a new LiteralExpression to replace it.
Then, you need to build child classes for all expressions you will want (for example, here is addition):
public class AdditionExpression : Expression
{
public AdditionExpression(Expression x, Expression y)
: base(x, y) { };
public override float Evaluate()
{
return lhs.Evaluate() + rhs.Evaluate();
}
}
Not how this is all the code we have to write for each expression - all the heavy lifting is handled by the abstract class. There are a few design flaws in this program - it won't detect circular references, and it won't stop you from passing null
values to expressions (which in the case of LiteralExpression is required), but those problems shouldn't be too hard to fix.
Then, all that needs to be done is to implement all of the child classes that inherit from expression.
There may be better methods to doing this, but from an OO viewpoint this is a nice example of making an generic abstract class that implements common behaviours and having a lot of small, specific implementations for each 'flavour' of that class.