我刚开始OOP,我有一个问题是如何在这种情况下正确设计我的类:
想象一下,我想创建一个代数库。我将任何表达式表示为树,每个节点要么是叶变量或常量,要么是父运算,如+或^。
我还希望这个表达式能够以多种不同的方式进行评估。我想允许使用不同的方法将表达式求值为float、int或rational等,因为每种求值方法都有不同的优点和缺点。
这意味着我有两种不同的方式,我的库的潜在用户可能想要扩展我的表达式类——要么添加一个新的节点类型(即一个新操作(,要么添加一种新的求值方法。
为了允许添加新的节点类型,我将使用继承-我可以使用一个抽象基类来表示一个表达式,然后库的用户可以简单地覆盖这个类来创建一个新的操作:
abstract class Expression
{
public abstract float Evaluate();
}
class Addition : Expression
{
Expression a;
Expression b;
public override float Evaluate()
{
return a.Evaluate() + b.Evaluate();
}
}
etc...
但是为了允许添加新的评估方法,我将使用一个单独的评估类将评估方法从Expression类中移出:
abstract class Expression
{
public abstract float Evaluate();
}
class Addition : Expression
{
Expression a;
Expression b;
}
abstract class ExpressionEvaluator<T>
{
public abstract T Evaluate(Expression e);
}
class FloatExpressionEvaluator : ExpressionEvaluator<float>
{
public override float Evaluate(Expression e)
{
switch (e)
{
case Addition addition:
return Evaluate(addition.a) + Evaluate(addition.b);
etc...
}
}
}
但是这种方法不允许添加额外的操作。它还感觉很容易出错,因为在编译时没有检查所有类在evaluate方法中是否都有case。
有没有任何方法可以让Expression类在两个方向上都可以扩展,并在编译时检查所有表达式是否可以在所有求值方法中使用?
有很多方法,但由于您想要一种通用方法,我建议您使用MiscUtil
,它是一个具有通用运算符(Add、Subtract..等(的通用库。您可以在代码中使用它,它会为您简化操作。
对于运算符,不需要为每个运算符创建一个类,因为它们的功能是一对一的。例如,Addition
类将只包含加法运算符(a+b(。它没有复杂的数学运算。所以,我建议对这些运算符使用method
。并专注于主要课程。主类可以根据需要包含许多运算符。有些很简单(如加法、减法等(,有些可能很复杂。对于简单的运算符,请保持简单,而对于复杂的运算符,您可能需要为它们实现一些类来简化它们。
对于实现设计本身,我认为Fluent API在您的情况下会更有益。所以,想象一下库的用法是这样的:
// would result 30 (as double).
var result =
new ExpressionOperator<double>(15.5)
.Add(15)
.Subtract(0.5)
.Evaluate();
这将像这样实现(使用MiscUtil
-lib(:
public sealed class ExpressionOperator<T> where T : struct
{
private T _result;
public ExpressionOperator(T a)
{
_result = a;
}
public ExpressionOperator<T> Add(T b)
{
_result = Operator.Add(_result, b);
return this;
}
public ExpressionOperator<T> Subtract(T b)
{
_result = Operator.Subtract(_result , b);
return this;
}
public T Evaluate() => _result;
public static explicit operator T(ExpressionOperator<T> exp) => exp._result;
}
然后,这取决于你将如何使用它并扩展它以满足你的需求。