逻辑表达式的解析器和计算器的重构建议
Refactoring advice for parser and calculator for logical expressions
我有一个逻辑表达式解析器和计算器的代码。而且我不太喜欢它的外观。
作为背景。
op运行ds
支持以下操作
bool: OR, And, Not
int: 大于、小于
bool 和 int:等于
我已经创建了通用接口 IElement
来描述表达式的任何元素。还有其他接口:
IBinary : IOperation
IValue : IElement
IOperation : IElement
IVariable<T> : IElement, IValue
IBinaryOperation<T> : IOperation, IBinary
IUnaryOperation : IOperation
并且这些接口接口具有以下 类 作为实现
Variable<T> : IVariable<T>
Not : IUnaryOperation
And : IBinaryOperation<bool>
Or : IBinaryOperation<bool>
LessThen : IBinaryOperation<int>
GreaterThen : IBinaryOperation<int>
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
案例,我已经实现了 IComparable
到 Variable<T>
的接口。
我有一个逻辑表达式解析器和计算器的代码。而且我不太喜欢它的外观。
作为背景。
op运行ds
支持以下操作bool: OR, And, Not
int: 大于、小于
bool 和 int:等于
我已经创建了通用接口 IElement
来描述表达式的任何元素。还有其他接口:
IBinary : IOperation
IValue : IElement
IOperation : IElement
IVariable<T> : IElement, IValue
IBinaryOperation<T> : IOperation, IBinary
IUnaryOperation : IOperation
并且这些接口接口具有以下 类 作为实现
Variable<T> : IVariable<T>
Not : IUnaryOperation
And : IBinaryOperation<bool>
Or : IBinaryOperation<bool>
LessThen : IBinaryOperation<int>
GreaterThen : IBinaryOperation<int>
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
案例,我已经实现了 IComparable
到 Variable<T>
的接口。