連載
改訂版
|
|
|
本記事は、(株)技術評論社が発行する書籍『VB6プログラマーのための入門 Visual Basic .NET 独習講座』の一部分を許可を得て転載したものです。同書籍に関する詳しい情報については、本記事の最後に掲載しています。 |
さて、前ページのソースを見てどう思われるだろうか? もちろん、このソースはよい例ではない。今回のテーマであるポリモーフィズムは、このソースをより分かりやすく、より短くするために利用できる。だが、これを利用する前に、少しこのソースの弱点を分析してみよう。
パッと見てすぐに気付くのは、出力する方法を数値で区別する方法をとっていることだろう。変数の値が1のときはイベント・ログへの出力としているが、そのようなルールは書き間違いひとつで破れてしまう。うっかり、その値が2のときにイベント・ログに出力するようなコードがソース・コード上に紛れ込んでも、それがバグであると気付くのは容易ではない。常識的に考えれば、数値に名前を当てるべきだろう。Enumステートメントで数値に名前を与えてみた例が、リスト10-37である。
|
|
リスト10-37 出力手段を数値からEnumステートメントによる列挙値に変更したプログラム(太字部分が変更箇所、“……”部分は一部省略) |
この変更により、“Case 1”のような記述が“Case ReportType.ToEventLog”に変わり、より意味が分かりやすくなった。Case ReportType.ToEventLogに対応する処理として、イベント・ログ以外に出力するコードが書かれていたら、すぐにおかしいと気付くことができるだろう。しかし、毎回毎回、エラーが起こるたびにSelect文を書いていたのではソースが長くなるし、出力先が増えるごとに正しくすべてのSelect文を書き直す手間は、プログラムが大きくなればなるほど大変なものである。やはり、Select文は1カ所にまとめるのが正解だろう。そのために、新しくReportメソッドを作成して、エラーの報告はすべてこのメソッドを呼び出すように変更すれば、エラー送信先が増えても減ってもソース修正の手間が小さいものとなる。リスト10-38は、実際にReportメソッドを作成してみた例である。
|
|
リスト10-38 Reportメソッドを作成して、その中にSelect文を記述したプログラム(太字部分が変更箇所、“……”部分は一部省略) |
ここまで直してもまだ不満が残る。というのも、エラー報告でReportメソッドを呼び出すというのは、あくまでプログラマーにとっての約束事であって、このメソッドを呼ばないで、いきなりReportToEventLogメソッドを呼び出してイベント・ログに書き込ませることもできるからだ。これは、クラスとPrivateキーワードを活用することで解消できる。
エラー報告関連の機能を別のクラスとしてまとめ、Reportメソッド以外にはPrivateキーワードを付ける。このような構造にしておけば、フォーム側からReportメソッド以外を呼び出すコードを記述するとエラーになるため、多人数のプロジェクトチームでプログラムを開発する場合でも、勘違いして誤用するプログラマーが出てくるのを防止できる。
実際には、エラー報告クラスには、報告方法を指定するためのメソッドもPublic付きで用意する必要があるので、ReportメソッドだけがPublic指定というわけにはいかない。実際に、このような構造に書き直してみたのが以下のリスト10-39である。
|
|
リスト10-39 エラー報告関連の機能を別のクラスReporterにしたプログラム(“……”部分は一部省略) |
このソースならクラスも利用しているし、Privateキーワードによるアクセス制御も有意義に活用されており、オブジェクト指向の世界に足を踏み入れたかのように思えるかもしれない。だが、オブジェクト指向の世界では、こういう問題を扱うときに、もっとよい方法が使われる。ここまで長々とソース・コードの初歩的な改良方法を解説してきたのは、ポリモーフィズムの利用がこのような改良とはまったく別次元の改善であることを、深く印象づけていただきたいからだ。
オブジェクト指向の世界では、このような改善方法とは異なる「ポリモーフィズム」という解決方法が存在する。実際に、まったく同じ機能を、ポリモーフィズムを用いて実現した例をリスト10-40に示す。
|
|
リスト10-40 ポリモーフィズムを用いて記述したプログラム |
ポリモーフィズムに慣れていない方は、とっさに何が書かれているのかわからないかもしれない。だが、一度理解してしまえば、難しいものではない。
ポリモーフィズムを理解するポイントは、実行時に呼び出すべきメソッドが決定されるという点である。このサンプル・プログラムの場合、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プログラミング | ||
Chapter 10 継承とポリモーフィズム | ||
1.Inheritsステートメントによる継承機能/クラス内で継承元クラスのメソッドを呼ぶ | ||
2.Windows FormにおけるInheritsステートメントの効果/継承時にメソッドの動作を入れ替える/Overridesキーワードを使って継承時にメソッドの動作を入れ替える | ||
3.中身のないメソッドに対し、継承により中身を入れる/Protectedアクセス/継承を禁止するNotInheritableキーワード | ||
4.ポリモーフィズム(異なる機能と共通の呼び出し方法) | ||
5.ポリモーフィズム(ソース・コードの弱点を分析/ポリモーフィズムを用いた書き換え) | ||
6.クラス・ライブラリに見るポリモーフィズムの実例 - その1/その2 | ||
「改訂版 プロフェッショナルVB.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用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|
- - PR -