佐々木屋

技術的なことから趣味まで色々書きます

デリゲートの使いどころ ①(共通処理)

デリゲートは作成するアプリケーションの性質によっては全くお目にかからない(使う必要にならない)機能ですので、イマイチ使いどころが分からないかもしれません。そこで、デリゲートの恩恵が受けられる場面を3回に渡って説明しようと思います。

共通処理を動的に分ける

例えば以下の処理を考えます。ボタン特有の処理が共通処理の間に挟まれている状況です。

private  void button1_Click(object sender, EventArgs e) {
    Console.WriteLine("共通処理");
    Console.WriteLine("button1の特別な処理");
    Console.WriteLine("共通処理");
}

private  void button2_Click(object sender, EventArgs e) {
    Console.WriteLine("共通処理");
    Console.WriteLine("button2の特別な処理");
    Console.WriteLine("共通処理");
}

なお、わかりやすいようにConsole.WriteLineを使用していますが、実際はもう少し長い処理を考えます。


このプログラムをリファクタリングしようと思った時、少しでもプログラミングをしたことがある方なら、処理を別メソッド(VB.NETで言うサブプロシージャ)化しちゃえばいいと言うでしょう。

つまり、以下のような処理に変更するのではないでしょうか。

private  void button1_Click(object sender, EventArgs e) {
    KyotuSyori(1);
}
private  void button2_Click(object sender, EventArgs e) {
    KyotuSyori(2);
}

private void KyotuSyori(int flg) {
    Console.WriteLine("共通処理");

    if (flg == 1) {
        Console.WriteLine("button1の特別な処理");
    }
    else {
        Console.WriteLine("button2の特別な処理");
    }
    
    Console.WriteLine("共通処理");
}
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    KyotuSyori(1)
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    KyotuSyori(2)
End Sub

Private Sub KyotuSyori(ByVal flg As Integer)
    Console.WriteLine("共通処理")

    If flg = 1 Then
        Console.WriteLine("button1の特別な処理")
    Else
        Console.WriteLine("button2の特別な処理")
    End If

    Console.WriteLine("共通処理")
End Sub



共通処理をまとめて、引数flgによって特別な処理を分岐する方法です。これで未来永劫完結する場合であれば問題ありませんが、button3が増えるとどうなるでしょうか。

当然flgがもう一つ増えますので、elseifかswitch(VB.NETのSelect Case)する必要があります。これでは手続き型と何ら変わりませんし、主要処理をさわる必要がありますので当然バグチェックはbutton1とbutton2もbutton3と同じようにする必要があります。つまり改廃作業にとてつもなく長い時間と労力が必要になります。

また、変更が発生しなくなったとしても、flg=1がbutton1という関連性をいつまで覚えておく必要がありますので、何か問題があってコードを読み返した時、非常に面倒なことになります。


さて、ここでデリゲートの登場です。もし、特別処理が以下の原則に一致するようであれば、デリゲートを使えば変更に強いコードにすることが可能です。
・引数はデリゲートするメソッドの引数(数、型両方)と同一でなければならない
・戻り値はデリゲートするメソッドの戻り値と同一でなければならない


private delegate void testDlgt();

private  void button1_Click(object sender, EventArgs e) {
    testDlgt d = new testDlgt(button1_toku);
    KyotuSyori(d);
}
private  void button2_Click(object sender, EventArgs e) {
    testDlgt d = new testDlgt(button2_toku);
    KyotuSyori(d);
}

private void KyotuSyori(testDlgt dlgt) {
    Console.WriteLine("共通処理");
    dlgt();
    Console.WriteLine("共通処理");
}

private void button1_toku() {
    Console.WriteLine("button1の特別な処理");
}
private void button2_toku() {
    Console.WriteLine("button2の特別な処理");
}
Private Delegate Sub testDlgt()

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim d As New testDlgt(AddressOf button1_toku)
    KyotuSyori(d)
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    Dim d As New testDlgt(AddressOf button2_toku)
    KyotuSyori(d)
End Sub

Private Sub KyotuSyori(ByVal dlgt As testDlgt)
    Console.WriteLine("共通処理")
    dlgt()
    Console.WriteLine("共通処理")
End Sub

Private Sub button1_toku()
    Console.WriteLine("button1の特別な処理")
End Sub
Private Sub button2_toku()
    Console.WriteLine("button2の特別な処理")
End Sub



メソッドの数は増えましたが、デリゲートを導入しただけで以下のメリットが増えました。
・フラグ定数による条件分岐が無くなった
・フラグ定数の意味を覚える必要が無くなった
・button3などが増えた場合はメソッドを追加してデリゲートにするだけでよい
・button3などが増えた場合はbutton3のみのテストをすればよい