いくつかの例で、メソッドがパラメーターとして渡されるのを見てきました。あるメソッドを別のメソッドから呼び出すことができる場合、メソッドをパラメーターとして渡す必要があるのはなぜですか? このデザインの背後にある目的は何ですか?
- あるメソッドから別のメソッドを呼び出す
- デリゲートまたはメソッドをパラメーターとして渡す
Action
メソッドをパラメーターとして渡すと、依存関係と結合を防ぐことができます。これを Strategy パターンにどのように使用できるかを見てみましょう。
PrintReport
パラメータに基づいて Name または Type でソートされたアイテムの特定のリストを出力するmethodがあるとします。これは素朴なアプローチです:
public void PrintReport (List<Item> data, SortOrder sortBy)
{
List<Item> sortedItems;
switch (sortBy)
{
case SortOrder.Name: sortedItems = SortByName(data); break;
case SortOrder.Type: sortedItems = SortByType(data); break;
}
Print(sortedItems);
}
シンプルですが、うまくいきます。しかし、新しいソート順を追加したい場合はどうなるでしょうか? SortOrder 列挙型を更新し、新しいものPrintReport
を追加して新しいメソッドcase
を呼び出す必要があります。SortByWhatever
しかし、メソッドをパラメーターとして渡した場合は、PrintReport
より単純になり、並べ替えの実装を気にする必要がなくなります。
public void PrintReport (List<Item> data, Func<List<Item>, List<Item>> sorter)
{
List<Item> sortedItems = sorter(data);
Print(sortedItems);
}
いずれにせよ、ソート関数を定義できるようになりました。おそらく、PrintReport
認識されていない別のアセンブリでも定義できます。ラムダ関数またはアドホックに定義された無名メソッドにすることができます。ただし、すべての場合において、このメソッドはデリゲートを受け取り、それを使用して並べ替えを行い、レポートを出力します。
使用例を示します。最初は、スイッチ/ケースを関数の外側に移動しただけのように見えます。これは、異なる呼び出し元が異なるロジックを持つことができるため、十分に重要です。ただし、3 番目のケースに注意してください。
public void HandleData()
{
switch (ReportItemOrder)
{
case SortOrder.Name: PrintReport(data, SortByName); break;
case SortOrder.Type: PrintReport(data, SortByType); break;
case SortOrder.Whatever:
Func<List<Item>, List<Item>> customSort = (items) => /* do something */;
PrintReport(data, customSort);
}
}
デリゲートは、通常、クラスとインターフェイスを互いに分離するために使用されます。
これが具体的な例です。カレンダーの描画を担当する UI クラスがあり、DateTime 値を文字列にフォーマットする正確な方法をクラスに認識させたくないとします。
次のようなクラスを定義できます。
public sealed class MyCalendarDrawer
{
private readonly Func<DateTime, string> _dateFormatter;
public MyCalendarDrawer(Func<DateTime, string> dateFormatter)
{
_dateFormatter = dateFormatter;
}
public void Draw()
{
// Do some work that involves displaying dates...
DateTime date = DateTime.Now;
string dateString = _dateFormatter(date);
// Display dateString somehow.
}
}
そうMyCalendarDrawer
すれば、日付をフォーマットする方法を知る必要はありませんFunc<DateTime, string>
。そうするために呼び出すことができるデリゲートを渡すことによって、その方法を教えられます。
真実は、関数が他の関数に渡されるすべての例は、関数に渡される特定のインターフェイスを実装するオブジェクトの観点から表現できるということです。
つまり、デリゲートがインターフェイスよりも優れている明確な理由はありません。Java の今後のラムダは、簡潔な構文を持つために関数を別の関数に渡すことができる必要がないという例です。
さらに別の言葉で言えば、関数を別の関数に渡す機能は、オブジェクトを関数に渡すのと同じように、プログラマーのツールキットの単なるツールです。どちらが優れているかは議論の余地がありますが、関数を関数に渡すことをまったくサポートしていない言語 (Java) を使用しても、同じ表現力を持つことができます。
関数をファーストクラスの型として扱うことには利点があります。関数型プログラミングの可能性を提供します。
たとえば、「イベント処理」の古典的なケースを考えてみましょう。イベント発生時のコールバックとして、別の関数への関数ポインタを確実に送信します。
同様に、これは別の架空の例です
private void CallMeBack(out int type, Func<int> action)
{
type = action();
}
CallMeBack(a, ()=>return 1);
これで、これに任意の機能を提供できます。CallMeBack(a, ()=>return 2);
Delegates について読む必要があります。例として、デリゲートは、特定のメソッドの完了時に動的コールバックを定義するのに役立ちます。
擬似コードの例:
doSomething(); //your code
updateInterface(continueDoingSomething); //a generic method, passing a delegate
...
doAnythingElse();
updateInterface(continueDoingAnythingElse);
この例では、デリゲートとして渡された動的メソッドをコールバックとして呼び出す汎用メソッド「updateInterface」を定義できます。
デリゲートを使用しない場合は、2 つ (またはそれ以上) の異なるメソッドを実装する必要があります。
void updateInterfaceAndContinueDoingSomething(){}
void updateInterfaceAndContinueDoingAnythingElse(){}