2

演算子ノード(単項演算子または二項演算子) または終端ノード(定数)のいずれかになる多数のノードを持つ関数ツリーがあるとします。

キーボードに指を入れ始めたばかりで、小さな引っ掛かりがありました。Nodeインターフェイス 、Operator:NodeBinaryOperator:OperatorUnaryOperator: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# でどのように構造化しますか?

4

2 に答える 2

3

Linq.Expressionsがどのように機能するかを見てみませんか?これは、C#でのこれ(および.NET言語全体)の既存のモデルです。

ここに、 Linq.Expressionsを使用して実装された派生プログラムの良い例があります(演算子、サイン、コサインなどをどのように処理するかを確認できます...)

于 2012-07-12T03:37:00.480 に答える
0

定義してみましょうSinOperator:UnaryOperator

GetValueでvirtualを作成したと仮定すると、Node期待する単一の子ノードを計算することによって取得されたオペランドを操作することにより、数値の Sin を計算するSinOperatorの実装を提供する必要があります。GetValue

「単一の子ノードを計算する」というこのタスクはそれぞれに冗長であるため、本質的にクラスにUnaryOperator属し、単一の引数を受け入れて返すメソッドをオーバーライドする必要があります。UnaryOperatorSinOperatorEvaldoubledouble

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 の作成を一元化し、常に一貫性を保つことができるように、ファクトリを使用することをお勧めします。後で、クラスを簡単に作成できるSineよりも高速な新しい計算方法に出くわした場合、ファクトリ プラントでこのクラスを.Math.SinSinFunctionFastSinFunction

Java のような言語では、おそらくこのようになるでしょうが、C# ではデリゲートが許可されているため、デリゲートを使用して多くの関数クラスを定義することを避けることができます。(デリゲートは、SinFunction などのクラスを定義する簡単な方法です)。

これを言って、私はあなたの現在の考えに対してあなたに警告したいと思います:

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

任意のデリゲートから保護するには、Math.Sin 自体をデリゲートに渡しUnaryOperator、単項デリゲートを想定する必要があります (つまり、double 型の引数を 1 つ受け取り、double を返す)。

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

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

この場合も、さまざまなオペレーターを作成するためにファクトリーを使用することをお勧めします。

ここでデリゲートを使用することの潜在的な欠点の 1 つは、C# ではマルチキャスト デリゲートの供給を防ぐ方法がないことですが、あなたの場合はユニキャスト デリゲートのみが必要です。また、Microsoft のガイドライン(特に の例に注意してくださいIComparable) によると、この特定の実装ではデリゲートではなくインターフェイスを選択する必要があります。

インターフェイスに対するユニキャスト デリゲートの利点の 1 つは、わずかに高速であることです。しかし、これから考えられるパフォーマンスの向上を得るには、多くの演算子を持つ巨大な関数ツリーが必要になります。

于 2012-07-12T10:40:47.997 に答える