2

想象一下,您有一个函数树,其中有许多节点,这些节点可以是运算符节点(一元或二元运算符),也可以是终端节点(常量)。

我刚刚开始将手指放在键盘上,但遇到了一个小障碍。我创建了一个接口Node、、、Operator:NodeBinaryOperator:Operator构造。UnaryOperator:OperatorTerminal:Node

我的问题在这里。我现在是否为每个可预见的功能创建一个单独的类?我要创建一个SinOperator:UnaryOperator等等CosOperator:UnaryOperatorAddOperator:BinaryOperator?如果您包括正向和反向三角函数及其双曲表亲,则可以考虑很多。

或者,我可以将它留在二元和一元运算符,并传入一个委托,该委托根据其子节点评估该节点的值。

var sinOperator = new UnaryOperator(
                    childNode,
                    delegate()
                    {
                      return Math.Sin(childNode.GetValue());
                    });

但是实际上并没有什么能阻止我在代表中放入各种疯狂的东西,打破了它只是一个操作员的整个概念。

注意:是的,我意识到只有几个运算符(+-*/^√),而 sin/cos 实际上是函数……但出于本项目的目的,我们可以假设它是一个运算符

那么你将如何在 C# 中构造它呢?

4

2 回答 2

3

为什么不看看Linq.Expressions是如何工作的?它是 C# 中这种(以及整个 .NET 语言)的现有模型。

在这里,您有一个很好的使用 Linq.Expressions 实现的派生程序示例(您可以检查它如何处理运算符、正弦、余弦等...)

于 2012-07-12T03:37:00.480 回答
0

让我们尝试定义SinOperator:UnaryOperator

假设你已经做了GetValuevirtual on NodeSinOperator应该只提供一个实现,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 如何偏离节点“以特殊方式获取价值”的目标。相反,它已将其职责转变为“评估价值”。这是一个全新的职责,似乎不属于原来的继承链。

那么该怎么做呢?答案是组合(可以看作 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 并且始终保持一致。稍后,如果您发现一种新的更快的计算方式,SineMath.Sin可以轻松创建一个SinFunctionFast类,并在您的工厂中种植这个类而不是SinFunction.

在像 Java 这样的语言中,您可能会这样做,但由于 C# 允许委托,您可以使用委托来避免定义大量函数类。(无论如何,委托都是定义诸如 SinFunction 之类的类的简写)。

说了这么多,我想提醒您注意您目前的想法:

var sinOperator = new UnaryOperator(
                    childNode,
                    delegate()
                    {        
                      return Math.Sin(childNode.GetValue());
                    });

为了防止任意委托,您应该将 Math.Sin 本身传递给委托,并且UnaryOperator应该期望一元委托(即接受双精度类型的单个参数并返回双精度)。

var sinOperator = new UnaryOperator(childNode, Math.Sin);

//In UnaryOperator Class
public override double GetValue()
{
     return _OpDelegate(_childNode.GetValue());
}

在这种情况下,也建议使用工厂来创建不同的操作符。

在这里使用委托的一个潜在缺点是,在 C# 中,无法阻止提供多播委托,而在您的情况下,您只需要单播委托。此外,根据Microsoft 的指南(特别注意 的示例IComparable),您应该为此特定实现选择接口而不是委托。

单播委托相对于接口的一个优势是它们只是快了一个档次。但是为了从中获得任何可以想象的性能增益,您将需要一个包含大量运算符的巨大函数树。

于 2012-07-12T10:40:47.997 回答