逻辑表达式的解析器和计算器的重构建议

Refactoring advice for parser and calculator for logical expressions

我有一个逻辑表达式解析器和计算器的代码。而且我不太喜欢它的外观。

作为背景。

op运行ds

支持以下操作

bool: OR, And, Not

int: 大于、小于

bool 和 int:等于

我已经创建了通用接口 IElement 来描述表达式的任何元素。还有其他接口:

  1. IBinary : IOperation
  2. IValue : IElement
  3. IOperation : IElement
  4. IVariable<T> : IElement, IValue
  5. IBinaryOperation<T> : IOperation, IBinary
  6. IUnaryOperation : IOperation

并且这些接口接口具有以下 类 作为实现

  1. Variable<T> : IVariable<T>
  2. Not : IUnaryOperation
  3. And : IBinaryOperation<bool>
  4. Or : IBinaryOperation<bool>
  5. LessThen : IBinaryOperation<int>
  6. GreaterThen : IBinaryOperation<int>
  7. Eq : IBinaryOperation<int>, IBinaryOperation<bool>

表达式的单位在后缀表示法中表示为 List<Element>。 这是我的 Compute 方法和其中使用的几个方法。我对两种内部方法都有问题。

public static bool Compute(List<IElement> element)
            {
                Stack<IElement> stack = new Stack<IElement>();
                foreach (IElement elem in element)
                {
                    IElement tmp = elem;
                    switch (elem)
                    {
                        case IValue val:
                            {
                                stack.Push(val);
                                break;
                            }
                        case IBinary val:
                            {
                                HandleBinary(val, ref stack);
                                break;
                            }
                        case IUnaryOperation val:
                            {
                                HandleUnary(val, ref stack);
                                break;
                            }
                    }
                }
                return ((IVariable<bool>)stack.Pop()).getValue();
            }
    private static void HandleBinary(IElement elem, ref Stack<IElement> stack)
                {
    
                    switch (elem)
                    {
                        case And and:
                            {
                                Tuple<IVariable<bool>, IVariable<bool>> operands = GetBoolOperands(ref stack);
                                stack.Push(and.Execute(operands.Item2, operands.Item1));
                                break;
                            }
                        case Or or:
                            {
                                Tuple<IVariable<bool>, IVariable<bool>> operands = GetBoolOperands(ref stack);
                                stack.Push(or.Execute(operands.Item2, operands.Item1));
                                break;
                            }
                        case Eq eq:
                            {
                                Type t = stack.Peek().GetType().GetGenericArguments()[0];
                                switch (Type.GetTypeCode(t))
                                {
                                    case TypeCode.Int32:
                                        {
                                            Tuple<IVariable<int>, IVariable<int>> operands = GetIntOperands(ref stack);
                                            stack.Push(eq.Execute(operands.Item2, operands.Item1));
                                            break;
                                        }
                                    case TypeCode.Boolean:
                                        {
                                            Tuple<IVariable<bool>, IVariable<bool>> operands = GetBoolOperands(ref stack);
                                            stack.Push(eq.Execute(operands.Item2, operands.Item1));
                                            break;
                                        }
                                }
                                break;
                            }
                        case GreaterThan gt:
                            {
                                Tuple<IVariable<int>, IVariable<int>> operands = GetIntOperands(ref stack);
                                stack.Push(gt.Execute(operands.Item2, operands.Item1));
                                break;
                            }
                        case LowerThan lt:
                            {
                                Tuple<IVariable<int>, IVariable<int>> operands = GetIntOperands(ref stack);
                                stack.Push(lt.Execute(operands.Item2, operands.Item1));
                                break;
                            }
                    }
                }
             private static Tuple<IVariable<int>, IVariable<int>> GetIntOperands(ref Stack<IElement> stack)
            {
                return new Tuple<IVariable<int>, IVariable<int>>(
                    (IVariable<int>)stack.Pop(),
                    (IVariable<int>)stack.Pop());

            }
            private static Tuple<IVariable<bool>, IVariable<bool>> GetBoolOperands(ref Stack<IElement> stack)
            {
                return new Tuple<IVariable<bool>, IVariable<bool>>(
                    (IVariable<bool>)stack.Pop(),
                    (IVariable<bool>)stack.Pop());

            }

如您所见,这段代码有很多模式匹配和类型转换,这很昂贵,而且转换的数量令人担忧。

我觉得我利用多态性的尝试失败了,那些接口和实现的整个准备工作都被浪费了。

我试图重构这段代码,但总是 运行 遇到大量类型转换和模式匹配的问题。

有人可以建议另一种方法或指出一个明显但遗漏的错误。

代替所有的 switch case 语句,实际使用多态性可能会有所帮助。对于我的示例,我会丢弃接口并仅使用 类。

我建议您在 类 中添加一些方法。一个可以称为virtual void Evaluate(stack);。 switch-case 现在简化为单个虚拟方法调用:

Stack<Element> stack = new Stack<Element>();
foreach (Element elem in element)
{
    elem.Evaluate(stack);
}

个别情况要在相应的类(Variable, And, Or, ...)中实现:

abstract class Element
{
    public abstract void Evaluate(Stack<Element> stack);
}

class Variable<T> : Element, IComparable where T : IComparable
{
    public T Value { get; set; }
    
    public override void Evaluate(Stack<Element> stack)
    {
        stack.Push(this);
    }

    public override bool Equals(object obj)
    {
        return obj is Variable<T> var && EqualityComparer<T>.Default.Equals(Value, var.Value);
    }

    public override int GetHashCode()
    {
        return Value?.GetHashCode() ?? 0;
    }

    public int CompareTo(object obj)
    {
        if (obj is Variable<T> var)
            return Value.CompareTo(var.Value);
        throw new InvalidOperationException();
    }
}

abstract class Binary : Element {}

class And : Binary
{
    public override void Evaluate(Stack<Element> stack)
    {
        var op1 = (Variable<bool>)stack.Pop();
        var op2 = (Variable<bool>)stack.Pop();
        stack.Push(new Variable<bool>() { Value = op1.Value && op2.Value });
    }
}

class Eq : Binary
{
    public override void Evaluate(Stack<Element> stack)
    {
        var op1 = stack.Pop();
        var op2 = stack.Pop();
        stack.Push(new Variable<bool>() { Value = op1.Equals(op2) });
    }
}

class GreaterThan : Binary
{
    public override void Evaluate(Stack<Element> stack)
    {
        var op1 = (IComparable)stack.Pop();
        var op2 = (IComparable)stack.Pop();
        stack.Push(new Variable<bool>() { Value = op1.CompareTo(op2) > 0 });
    }
}

对于 Eq 的情况,我已经覆盖了默认的 Equals 方法(在这种情况下应该也覆盖 GetHashCode 方法,虽然这里没有必要)。对于 GreaterThan/LessThan 案例,我已经实现了 IComparableVariable<T> 的接口。