佐々木屋

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

デリゲートの使いどころ ②(保留)

デリゲートの使いどころとして、前回は共通処理をまとめる話をしました。
今回は2つ目「保留」です。

プログラミングを構築していると、長いコードの中でどうしても「クラスの宣言」と「メソッドの実行」を分けて行いたい(タイミングが異なる)場面が結構あるのです。デリゲート自体が「宣言」と「実行」が分かれていることを利用する方法です。

例えば以下のクラスを考えます。

public class Test1 {
    public Test1(string value) {
        header = value;
    }
    private string header = string.Empty;
    public void Test1Method(string naiyo) {
        Console.WriteLine(header + naiyo);
    }
}

public class Test2 {
    public Test2(string value) {
        header = value;
    }
    private string header = string.Empty;
    public void Test2Method(string naiyo) {
        Console.WriteLine(naiyo + header);
    }
}
Public Class Test1
    Public Sub New(ByVal value As String)
        header = value
    End Sub
    Private header As String = String.Empty
    Public Sub Test1Method(ByVal naiyo As String)
        Console.WriteLine(header & naiyo)
    End Sub
End Class

Public Class Test2
    Public Sub New(ByVal value As String)
        header = value
    End Sub
    Private header As String = String.Empty
    Public Sub Test2Method(ByVal naiyo As String)
        Console.WriteLine(naiyo & header)
    End Sub
End Class

クラスが2つあり、条件によって呼び出すクラスが異なるプログラムを想定しています。
変数nがあり、n=1の場合Test1クラスのTest1Methodメソッドを呼び、n=2の場合はTest2クラスのTest2Methodメソッドを呼びだします。但し、クラスの宣言(コンストラクタ)とメソッドの実行はタイミングの関係上一緒に行えないという状況を考えて下さい。

単純にプログラムを構築しようとすると、以下の流れになるはずです。
f:id:sasaki816:20190224223104j:plain
同じ条件分岐が2回出てしまいました。宣言と実行が分かれているので当然と言えば当然です。

それでは、このフローチャートをそのままコードにしてみます。

object cls = null;
for (int n = 1; n <= 2; n++) {
    if (n == 1) {
        cls = new Test1("Hello!");
    }
    else {
        cls = new Test2("World!");
    }

    /*・・・
    別の長い処理
    ・・・*/

    System.Reflection.MethodInfo meth = null;
    if (n == 1) {
        meth = cls.GetType().GetMethod("Test1Method");
    }
    else {
        meth = cls.GetType().GetMethod("Test2Method");
    }
    meth.Invoke(cls, new object[] { "hoge" });
}
Dim cls As Object = Nothing
For n As Integer = 1 To 2
    If n = 1 Then
        cls = New Test1("Hello!")
    Else
        cls = New Test2("World!")
    End If

    '・・・
    '別の長い処理
    '・・・

    Dim meth As System.Reflection.MethodInfo = Nothing
    If n = 1 Then
        meth = cls.GetType().GetMethod("Test1Method")
    Else
        meth = cls.GetType().GetMethod("Test2Method")
    End If
    meth.Invoke(cls, New Object() {"hoge"})
Next

2回目の条件分岐では遅延バインディングとなってしまいますので、リフレクションを利用して実行するメソッドを宣言しInvokeメソッドを実行する必要があります。つまり、宣言用の変数の他にリフレクション用の変数が追加で必要ということです。


さて、これをデリゲートを使った方法に変更するとどうなるでしょうか。早速やってみましょう。

delegate void TestDlgt(string naiyo);

TestDlgt dlgt;
for (int n = 1; n <= 2; n++) {
    if (n == 1) {
        dlgt = new TestDlgt(new Test1("Hello!").Test1Method);
    }
    else {
        dlgt = new TestDlgt(new Test2("World!").Test2Method);
    }

    /*・・・
    別の長い処理
    ・・・*/

    dlgt("hoge");
}
Delegate Sub TestDlgt(ByVal naiyo As String)

Dim dlgt As TestDlgt
For n As Integer = 1 To 2
    If n = 1 Then
        dlgt = New TestDlgt(AddressOf New Test1("Hello!").Test1Method)
    Else
        dlgt = New TestDlgt(AddressOf New Test2("World!").Test2Method)
    End If

    '・・・
    '別の長い処理
    '・・・

    dlgt("hoge")
Next

このようにフローチャート上のピンクの処理が全て消えてしまいました。デリゲートを利用することで、メソッドの実行自体を「保留」することが可能なのです。


なお、ポリモーフィズムの概念を利用すれば、1回目の条件分岐も消すことが可能ですが、今回は実行するメソッドの保留を分かりやすく説明するため、敢えてポリモーフィズムは無視した設計をしています。

逆に言えば、今回のテーマを読んでいて途中でそれに気が付いた方は、オブジェクト指向型言語がよく分かってきている証拠とも言えます。