web-dev-qa-db-ja.com

OOPオブジェクト構成からFP C#の関数構成に移動する方法

私は新しいWebプロジェクトで数週間働いており、私がしているすべては基本的にデータの計算と変換であり、ほとんどのクラスには状態が含まれておらず、完全に静的クラスに変換できることに気付いています静的メソッド。したがって、関数型プログラミングのパラダイムに従ってアプリケーションを構築する良い機会になると思います。私は最初にインターネットでいくつかの調査を行って、他の人が同様のことを試みている体験について読みました。いくつかの興味深いアイデアを持つニース ブログエントリ を見つけました。

ただし、ネストされた関数呼び出しの複数のレベルがある状況にどのように対処する必要があるのか​​と思います。最初にOOPで、次にFPで、という意味です。

OOPでは、通常、コンポジションを使用してレイヤーに機能を構築する単純なクラスを作成しようとします。次のようなもの:

_public class ClassLevel1
{
    private readonly IClassLevel2 _objLevel2;

    public ClassLevel1(IClassLevel2 objLevel2)
    {
        _objLevel2 = objLevel2;
    }

    public int FuncLevel1(int param1, int param2)
    {
        // Some operations here
        var res = _objLevel2.FuncLevel2(param1) + param2;
        // Some more operations here with the 'res'
        return result;
    }  
}

public class ClassLevel2
{
    private readonly IClassLevel3 _objLevel3;

    public ClassLevel2(IClassLevel3 objLevel3)
    {
        _objLevel3 = objLevel3;
    }

    public int FuncLevel2(int param1)
    {
        // Some operations here
        var res = _objLevel3.FuncLevel3(param1);
        // Some more operations here with the 'res'
        return result;
    }  
}
_

次に、依存性注入に依存してインスタンスを構築し、モックフレームワークを利用して、各クラスのユニットテストを非常に簡単に行うことができます。

ここで、FP=に移動したいとし、内部で3番目の関数_FuncLevel1_を内部的に使用する_FuncLevel2_を呼び出す関数_FuncLevel3_があるとします。 SOMECODE)__。私の主な目標の1つは、ユニットテストの各関数を簡単にモックできるようにコードを構造化することです。たとえば、コードは次のようになります。

_public static int FuncLevel1(
    Func<Func<int, int>, int, int> FuncLevel2,
    Func<int, int> FuncLevel3,
    int param1,
    int param2)
{
    // Some operations here
    var res = FuncLevel2(FuncLevel3, param1) + param2;
    // Some more operations here with 'res'
    return result;  
}

public static int FuncLevel2(
    Func<int, int> FuncLevel3,
    int param1)
{
    // Some operations here
    var res = FuncLevel3(param1);
    // Some more operations here with 'res'
    return result;  
}
_

次に、関数_FuncLevel1_が次のように呼び出されます。

_var res = FuncLevel1(FuncLevel2, FuncLevel3, 5, 10);
_

この構造の利点は、それぞれ_FuncLevel2_と_FuncLevel3_を単体テストする場合に、関数_FuncLevel1_と_FuncLevel2_をモックできることです。

単純なシナリオでは、代わりに次のようにコードを記述できることを理解しています。

_var res3 = FuncLevel3(5);
var res2 = FuncLevel2(res3) + 10;
var res1 = FuncLevel1(res2);
_

しかし、このような問題を解決できない他の(より複雑な)状況が存在する可能性があることは容易に想像できます。

深くネストされた関数呼び出しがある場合、必要なすべての関数を下位レベルですべて上から渡すと、厄介なコードが生成される可能性があります。したがって、私はこのようなシナリオをより適切に処理するための代替案について読むことに非常に興味があります。

4

F#のような関数型言語では、 部分的なアプリケーション を利用できます。つまり、FuncLevel1には関数パラメータFuncLevel2が1つだけあり、その内部にはすでにFuncLevel3が含まれています。

let funcLevel1 (funcLevel2 : int -> int) (param1 : int) (param2 : int) : int =
    let result = funcLevel2 param1 + param2;
    result

let funcLevel2 (funcLevel3 : int -> int) (param1 : int) : int =
    let result = funcLevel3 param1
    result

使用法:

let result = funcLevel1 (funcLevel2 funcLevel3) 5 10

C#では、関数も同じ方法でモデル化できます。

public static int FuncLevel1(
    Func<int, int> FuncLevel2,
    int param1,
    int param2)
{
    var result = FuncLevel2(param1) + param2;
    return result;  
}

問題は、部分的な適用を実行する方法です。 C#は直接サポートしていませんが、ラムダを使用してエミュレートできます。

var res = FuncLevel1(i => FuncLevel2(FuncLevel3, i), 5, 10);

別のオプションは、部分的なアプリケーションを実行するヘルパーメソッドを記述することです。

public static Func<T2, T3> PApply<T1, T2, T3>(Func<T1, T2, T3> func, T1 arg1)
{
    return arg2 => func(arg1, arg2);
}

しかし、それを使用しようとすると:

var res = FuncLevel1(PApply(FuncLevel2, FuncLevel3), 5, 10);

型引数を推測できないというコンパイル時エラーが発生します。そのエラーは修正可能ですが、ラムダバージョンよりもコードが悪くなるため、そのまま使用します。

0
svick