想象一下,你有一个function tree,它有许多节点,可以是操作员节点(一元或二元运算符),或终端节点(常量) )。
我刚开始把手指放到键盘上,我遇到了一个小问题。我创建了Node
界面,Operator:Node
,BinaryOperator:Operator
,UnaryOperator:Operator
和Terminal:Node
结构。
在这里,我遇到了问题。我现在是否为每个可预见的功能创建一个单独的类?我是否要创建SinOperator:UnaryOperator
和CosOperator:UnaryOperator
以及AddOperator:BinaryOperator
等等?如果你包括正向和反向三角函数及其双曲线表兄弟,可以考虑很多。
或者,我可以将它保留在Binary和Unary运算符中,并传入一个委托,该委托根据它的子节点计算该节点的值。
var sinOperator = new UnaryOperator(
childNode,
delegate()
{
return Math.Sin(childNode.GetValue());
});
但是那时并没有什么能阻止我在代表中放入各种疯狂的东西,打破了它作为运营商的整个概念。
注意:是的我知道只有少数运算符(+ - * / ^√),而sin / cos实际上是函数。 ..但是出于本项目的目的,我们可以假设它是一个运算符
那么你将如何在C#中构建它?
答案 0 :(得分:3)
为什么不看看Linq.Expressions的工作原理?它是C#中现有的模型(以及整个.NET语言)。
在这里,您使用Linq.Expressions实现了nice example of a Derivation program(您可以检查它如何处理运算符,正弦,余弦等...)
答案 1 :(得分:0)
让我们尝试定义SinOperator:UnaryOperator
假设您在GetValue
上设置了Node
虚拟,SinOperator
应该只提供GetValue
的实现,它通过对由此获得的操作数进行操作来计算数字的Sin。计算它期望的单个子节点。
“计算单个子节点”的任务对于每个UnaryOperator
都是多余的,因此应基本上属于UnaryOperator
类,而SinOperator
必须自己覆盖Eval
方法接受单个double
参数并返回double
。
abstract class UnaryOperator:Operator //specializes GetValue
{
//....Rest of the implementatiom
//....
public sealed override double GetValue()
{
return Eval(_childNode.GetValue());
}
protected abstract double Operate(double arg);
}
class SinOperator:UnaryOperator
{
public override double Eval(double arg)
{
return Math.Sin(arg);
}
}
请注意SinOperator如何偏离Node的“以专业方式获取价值”的目标。相反,它已将其责任转变为“评估价值”。这是一项全新的责任,似乎不属于原始的继承链。
那怎么去呢?答案是组合(可以看作是UnaryOperator'具有计算一元函数的算法)。
interface UnaryFunction
{
double Eval(double arg);
}
class UnaryOperator:Operator //specializes GetValue
{
private UnaryFunction _evaluator; //For Sin this is SinFunction
//....Rest of the implementatiom
//....
public sealed override double GetValue()
{
return _evaluator.Eval(_childNode.GetValue());
}
}
class SinFuntion:UnaryFunction
{
public override double Eval(double arg)
{
return Math.Sin(arg);
}
}
如何获得SinOperator?我建议使用一个工厂,以便创建一个与Sinfunction绑定的UnaryOperator可以集中并始终保持一致。稍后,如果您使用新的更快的方式来计算Sine
而不是Math.Sin
,则可以轻松创建SinFunctionFast
类,并在工厂中设置此类而不是SinFunction
。< / p>
在像Java这样的语言中你可能会这样,但是,由于C#允许委托,你可以使用委托来避免定义很多函数类。 (代表们以任何方式都是定义诸如SinFunction之类的简写)。
话虽如此,我想提醒你不要反对你当前的想法:
var sinOperator = new UnaryOperator(
childNode,
delegate()
{
return Math.Sin(childNode.GetValue());
});
为了防止任何委托,你应该将Math.Sin本身传递给委托,UnaryOperator
应该期待一个一元委托(即接受double类型的单个参数并返回一个double)。
var sinOperator = new UnaryOperator(childNode, Math.Sin);
//In UnaryOperator Class
public override double GetValue()
{
return _OpDelegate(_childNode.GetValue());
}
在这种情况下,建议使用工厂来创建不同的操作员。
在这里使用委托的一个潜在缺点是,在C#中无法阻止多播委托的供应,而在您的情况下,您只需要一个Uni-cast委托。另外根据Microsoft's guidelines(特别注意IComparable
的示例),您应该为此特定实现选择接口而不是委托。
单播代表在接口上的一个优点是它们只是一个档次更快。但是为了从中获得任何可以想象的性能增益,你需要一个包含大量操作符的巨大功能树。