連載

プロフェッショナルVB.NETプログラミング

第19回 継承とポリモーフィズム

(株)ピーデー
川俣 晶
2002/10/05

Page1 Page2 Page3 Page4 Page5

ポリモーフィズムを用いた書き換え

 オブジェクト指向の世界では、このような改善方法とは異なるポリモーフィズムという解決方法が存在する。実際に、まったく同じ機能を、ポリモーフィズムを用いて実現した例を以下に示す。

  1: Imports System.Web.Mail
  2:
  3: Public MustInherit Class Reporter
  4:   Public MustOverride Sub ReportMessage(ByVal msg As String)
  5: End Class
  6:
  7: Public Class ToMessageBox
  8:   Inherits Reporter
  9:   Public Overrides Sub ReportMessage(ByVal msg As String)
 10:     MessageBox.Show(msg)
 11:   End Sub
 12: End Class
 13:
 14: Public Class ToEventLog
 15:   Inherits Reporter
 16:   Public Overrides Sub ReportMessage(ByVal msg As String)
 17:     If Not EventLog.SourceExists("SampleSource") Then
 18:       EventLog.CreateEventSource("SampleSource", "SampleNewLog")
 19:     End If
 20:     Dim myLog As New EventLog()
 21:     myLog.Source = "SampleSource"
 22:     myLog.WriteEntry(msg)
 23:   End Sub
 24: End Class
 25:
 26: Public Class ToEMail
 27:   Inherits Reporter
 28:   Public Overrides Sub ReportMessage(ByVal msg As String)
 29:     Dim from As String = "autumn@piedey.co.jp"
 30:     Dim mailto As String = "autumn@piedey.co.jp"
 31:     Dim subject As String = "Sample Error Report"
 32:     Dim body As String = msg
 33:     SmtpMail.Send(from, mailto, subject, body)
 34:   End Sub
 35: End Class
 36:
 37: Public Class Form1
 38:   Inherits System.Windows.Forms.Form
 39:
 40: …Windows フォーム デザイナで生成されたコード…
 41:
 42:   Private myReporter As Reporter
 43:
 44:   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
 45:     myReporter = New ToMessageBox()
 46:   End Sub
 47:
 48:   Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
 49:     myReporter.ReportMessage("エラーが発生しました")
 50:   End Sub
 51:
 52:   Private Sub RadioButton1_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles RadioButton1.CheckedChanged
 53:     myReporter = New ToMessageBox()
 54:   End Sub
 55:
 56:   Private Sub RadioButton2_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles RadioButton2.CheckedChanged
 57:     myReporter = New ToEventLog()
 58:   End Sub
 59:
 60:   Private Sub RadioButton3_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles RadioButton3.CheckedChanged
 61:     myReporter = New ToEMail()
 62:   End Sub
 63: End Class
今回のサンプル・プログラムをポリモーフィズムを用いて書き換えたサンプル・プログラム5

 ポリモーフィズムに慣れていないプログラマーはとっさに何が書かれているのか分からないかもしれない。だが、一度理解してしまえば、難しいものではない。

 ポリモーフィズムのポイントは、実行時に呼び出すべきメソッドが決定されるという特徴である。このサンプル・プログラムの場合、49行目でReportMessageメソッドを呼び出しているが、このメソッド呼び出しが具体的にどのメソッドを呼び出すかは、実行時に決定される。つまり、9行目のReportMessageメソッドを呼ぶのか、16行目のReportMessageメソッドを呼ぶのか、28行目のReportMessageメソッドを呼ぶのか、それは実行中に決定されるわけである。もちろん、実行時に決定できなければ、ラジオ・ボタンによって切り替えができないわけだから、ある意味で当然である。

 次に注目していただきたいのは、49行目のReportMessageメソッドの呼び出しが、Reporter型の変数myReporterに対して行われていることである。ここでの呼び出しは、あくまでReporter型、つまりReporterクラスのReportMessageメソッドを対象としたものであって、決して、ToMessageBoxクラスやToEventLogクラスを対象とはしていないのである。しかし、直接的に対象とされていないからといって、呼び出せないわけではない。実際に、この変数に代入されている値は、ToMessageBoxクラスやToEventLogクラスのインスタンスであることは、ソース・コードを見れば一目瞭然だろう。

 ここでポイントになるのは、2点である。1点は、ToMessageBoxクラスやToEventLogクラスのインスタンスへの参照を、どうしてReporter型の変数に代入できるのか。もう1点は、Reporterクラスのメソッドを呼び出しているのに、どうしてほかのクラスのメソッドが呼び出されるかである。

 前者は、インスタンスへの参照は常に自分のスーパークラスへの参照に変換可能であるという機能から実現可能なのは明らかだ。つまり、Aというクラスを継承したB、C、Dというクラスのインスタンスへの参照は、常にA型の変数に格納できる。そのため、異なる機能を持つクラスへの参照を保持したければ、それらのすべてのクラスに対するスーパークラスとなるクラスを作成しておけば簡単に実現できるわけである。

 後者は、仮想メソッドの機能を理解していれば、実現できることは容易に理解できるだろう。この場合、ReporterクラスのReportMessageメソッドは、MustOverrideキーワードが付加されており、中身が存在しない仮想的なメソッドになっている。そのため、Reporterクラスのインスタンスは作成できない。作成しても、中身のないReportMessageメソッドが呼び出せないので、インスタンスとして不完全なのである。しかし、このクラスは継承には使用できる。例えば、8行目でToMessageBoxクラスはReporterクラスを継承していることが示されている。そして、9〜11行目のReportMessageメソッドの定義は、ただ単にメソッドを定義しているわけではなく、Overridesキーワードにより、スーパークラス(つまりReporterクラス)の同名のメソッドの中身を入れ替えることを示している。つまり、ToMessageBoxクラスのインスタンスへの参照は、Reporterクラスへの参照に変換できるが、変換された場合でも、ReportMessageメソッドの呼び出しは、Reporterクラスではなく、ToMessageBoxクラスのメソッドを呼び出すのである。このように参照される型が変換されても、呼び出すメソッドが不変であるのが仮想メソッドの特徴である。

 その結果、49行目のように、常にReporterクラスのReportMessageメソッドを呼び出しているにもかかわらず、実際にはほかのクラスのReportMessageメソッドを呼び出している、という機能が実現できる。

 この機能を利用すると、実際に処理される機能は違っているが、表面的に同じような呼び出し方法で利用できる機能を、分かりやすく整理することが容易に実現できる。このサンプル・プログラムの場合、コンパクトさ、安全性、分かりやすさ、拡張性などが実現されていることが分かると思う。コンパクトさとは、レポートの出力を管理する専用クラスや出力の種類を示すための列挙型の定義などが不要になっていることを意味する。安全性とは、出力に使用できるクラスはすべてReporterクラスを継承したクラスでなければならず、そうではないクラスをReporter型の変数に入れようとしてもコンパイラがエラーを報告してくれることを意味する。分かりやすさとは、継承関係を見るだけで、出力に使用できるクラスとそうではないクラスを区別することができ、プログラマー間の約束事などをいちいち確認する必要がないことを意味する。拡張性というのは、このソースの場合、出力先の種類を増やすとき、ただ単にReporterクラスを継承したクラスを新しく作るだけでよい手軽さを意味する。列挙型で区別する場合は、列挙型の項目を増やし、それと同時に、列挙型の値を見て処理を区別するメソッドにも、判定項目を増やす必要がある。これが正しく同期して記述されなければ、正常には動作しないが、正しく同期させるのはプログラマーの仕事である。これに対して、ポリモーフィズムを用いた方法の場合、Reporterクラスを継承していないクラスのインスタンスを代入しようとしたり、継承していてもReportMessageメソッドを実装していないクラスを利用しようとしたりすれば、コンパイル時にエラーになり、間違いを早期に察知することができる。


 INDEX
  連載 プロフェッショナルVB.NETプログラミング
  第19回 継承とポリモーフィズム
    1.異なる機能と共通の呼び出し方法
    2.ソース・コードの弱点を分析
  3.ポリモーフィズムを用いた書き換え
    4.クラス・ライブラリに見るポリモーフィズムの実例
    5.さらにクラス・ライブラリの実例
 
「プロフェッショナルVB.NETプログラミング」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間