ポリモーフィズム(デリゲート)
デリゲート使用の説明①共通処理の場面で以下のコードを最後に紹介しましたが、実はこのコードには続きがあります。前回紹介したコードは以下の通り。
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 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
「確かに条件分岐はなくなったけど、少し冗長では?」と思った方、オブジェクト指向プログラミングの恩恵が少し分かってきたのではないでしょうか。
Button1とButton2の処理の違いはデリゲートするメソッドの違いだけです。そもそもデリゲート自体を配列化してしまい、それぞれのButtonのTagプロパティに番号を割り当ててしまえば、条件分岐をなくしつつ、イベントを統合できるという状況に出来ます。
では早速リファクタリングをしてみましょう。リファクタリングの流れとしては、
・デリゲート配列と紐づけるインデックス番号「固定番号」をシンボリック定数とする
・ButtonのTagプロパティに固定番号を割り当てる
・デリゲート配列を作成
・ボタンイベントの統合
の4段階です。
const int BTN1 = 0; const int BTN2 = 1; private delegate void testDlgt(); private List<testDlgt> dlgt; private void Form1_Load(object sender, EventArgs e) { dlgt = new List<testDlgt> { new testDlgt(button1_toku) , new testDlgt(button2_toku) }; button1.Tag = BTN1; button2.Tag = BTN2; } private void button_Click(object sender, EventArgs e) { KyotuSyori(dlgt[int.Parse((sender as Button).Tag.ToString())]); } private void KyotuSyori(testDlgt dlgt) { Console.WriteLine("共通処理"); dlgt(); Console.WriteLine("共通処理"); } private void button1_toku() { Console.WriteLine("button1の特別な処理"); } private void button2_toku() { Console.WriteLine("button2の特別な処理"); }
Const BTN1 As Integer = 0 Const BTN2 As Integer = 1 Private Delegate testDlgt() Private dlgt As List(Of testDlgt) Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load dlgt = New List(Of testDlgt) From {New testDlgt(AddressOf button1_toku), New testDlgt(AddressOf button2_toku)} Button1.Tag = BTN1 Button2.Tag = BTN2 End Sub Private Sub Button_Click(sender As Object, e As EventArgs) Handles Button1.Click, Button2.Click KyotuSyori(dlgt(Integer.Parse((DirectCast(sender, Button).Tag.ToString())))) 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
このリファクタリングによって、拡張性と保守性が大幅に上がります。例えば3番目のボタンが出現した場合、button3の特別な処理メソッドは追加するとして、それ以外必要なことはシンボリック定数とTagプロパティ設定、デリゲート配列の追加の3つだけで拡張できてしまいます。ここで重要なことは、既存プログラム(ここだとbutton_clickメソッドやKyotuSyoriメソッド)に一切変更を加えることなく機能を拡張できたという点です。
これはオブジェクト指向の話で必ずと言っていいほど出てくる「ポリモーフィズム」の概念が分かっているとすぐに気が付くと思います。
ポリモーフィズムは継承やインターフェースで出てくるオーバーロード、オーバーライド、シャドウの総称を指します。日本語で言うと「多態性」なんて表現しますが(分かりにくい!)、簡単に言えば同じ包丁(メソッド)でも用途によって自動的に変化してくれる機能みたいなものです。パンにはパン切り包丁、刺身には刺身包丁、中華には中華包丁みたいな感じです。
手続き型の場合は「都度用途に合った包丁を呼び出す」必要性がありましたが、ポリモーフィズムの概念を使えば、今回の例のように「今から作業するのに適した包丁出して」というと勝手に出てくる、こういった機能です。これがオブジェクト指向プログラミングの醍醐味の一つですね。