0

I'd like to create a model to represent math equations in XML. But I can't figure out how to without violating the open-closed principle.

For example:

<Math>
  <Multiply>
    <Constant Value="..."/>
    <Constant Value="..."/>
    <Constant Value="..."/>
  </Multiply>
</Math>

That would be the XML representation of this math equation:

math = Constant * Constant * Constant

As you know, math equations are extensible. In other words if we introduce addition then this is also a valid math equation:

<Math>
  <Multiply>
    <Constant Value="..."/>
    <Add>
      <Constant Value="..."/>
      <Constant Value="..."/>
    </Add>
  </Multiply>
</Math>

From the perspective of the C# model I know I want each Multiply, Add, Constant, etc class to implement this common interface:

public interface IMath
{
    double Solve();
}

So perhaps the models (sans serialization) will look like this:

public class Math : IMath
{
    public IMath? Child { get; set; }

    public double Solve() => Child?.Solve() ?? double.NaN;
}

public class Multiply : IMath
{
    public List<IMath> Children { get; } = new();

    public double Solve()
    {
        double? product = null;
        foreach (var child in Children)
        {
            var childSolution = child.Solve();
            if (product.HasValue)
            {
                product = product.Value * childSolution;
            }
            else
            {
                product = childSolution;
            }
        }
        return product ?? double.NaN;
    }
}

public class Constant : IMath
{
    public double Value { get; }

    public double Solve() => Value;
}

/* Use your imagination to see the `Add` class */

Now, I understand that I can do something like the following:

[Serializable]
[XmlRoot("Math")]
public class Math : IMath
{
    [XmlElement("Multiply", typeof(Multiply))]
    [XmlElement("Constant", typeof(Constant))]
    public IMath? Child { get; set; }

    public double Solve() => Child?.Solve() ?? double.NaN;
}

[Serializable]
[XmlRoot("Multiply")]
public class Multiply : IMath
{
    [XmlElement("Multiply", typeof(Multiply))]
    [XmlElement("Constant", typeof(Constant))]
    public List<IMath> Children { get; } = new();

    public double Solve()
    {
        double? product = null;
        foreach (var child in Children)
        {
            var childSolution = child.Solve();
            if (product.HasValue)
            {
                product = product.Value * childSolution;
            }
            else
            {
                product = childSolution;
            }
        }
        return product ?? double.NaN;
    }
}

...and so on.

But that doesn't seem very flexible. Because every single implementation of IMath has to know about every other implementation. Look what I have to do when I throw the Add class in the mix:

public class Math : IMath
{
    [XmlElement("Multiply", typeof(Multiply))]
    [XmlElement("Constant", typeof(Constant))]
    [XmlElement("Add", typeof(Add))] // Now Math has to know about Add
    public IMath? Child { get; set; }

    ...
}

public class Multiply : IMath
{
    [XmlElement("Multiply", typeof(Multiply))]
    [XmlElement("Constant", typeof(Constant))]
    [XmlElement("Add", typeof(Add))] // Now Multiply has to know about Add
    public List<IMath> Children { get; } = new();

    ...
}

public class Add : IMath
{
    // Add has to know about everyone else :'(
    [XmlElement("Multiply", typeof(Multiply))]
    [XmlElement("Constant", typeof(Constant))]
    [XmlElement("Add", typeof(Add))]
    public List<IMath> Children { get; } = new();

    ...
}

Is there an alternative that doesn't violate the open-closed principle? I'm fine with using reflection.


If the above code doesn't compile and work perfectly then please wave your hands wildly at it until it does

Matt Thomas
  • 5,279
  • 4
  • 27
  • 59

0 Answers0