第5回 継承を使わないとしても知っておくべきこと:連載 オブジェクト指向プログラミング超入門(3/3 ページ)
基本クラスの機能を受け継ぐだけが継承ではない。今回解説するような継承の機能こそがプログラミングでは実は重要だ。
Streamクラスとその派生クラス
もう1つ別の例を見ていきます。
プログラムでJPEGなどのビットマップを扱うときにはBitmapクラス(System.Drawing名前空間)を用いますが、このクラスのコンストラクタでは、読み込むJPEGファイルのファイル名を指定してBitmapオブジェクトを作成することができます(以下の例は、カレント・ディレクトリに「picture.jpg」というJPEGファイルが必要です)。
Bitmap bm = new Bitmap("picture.jpg");
フォームにPictureBoxコントロールが配置済みだとすると、次のようなコードによりPictureBoxコントロールのイメージとして、このJPEGファイルを表示できます(PictureBoxコントロールのインスタンスを変数pictureBox1で参照している場合)。
pictureBox1.Image = bm;
このように、Bitmapクラスのコンストラクタのパラメータとして文字列を指定した場合には、それはファイルのパス名として扱われます。さてここからが本題です。Bitmapクラスには、パラメータとしてStream型のオブジェクトを受け取るコンストラクタも用意されています*2。
*2 C#やVB.NETでは、パラメータの個数や型が異なる複数のコンストラクタを定義することができます。これは「コンストラクタのオーバーロード」と呼ばれます。コンストラクタがオーバーロードされている場合、どのコンストラクタが実行されるかは、呼び出したコンストラクタのパラメータの個数や型によって決定されます。
■Streamクラスのクラス階層
System.IO名前空間にあるStreamクラスは、後述するように直接インスタンスを作成することはできません。クラス・ライブラリでは、このクラスを継承した派生クラスがいくつか定義されています。次の図8では代表的な3つの派生クラスを示しています。
FileStreamクラスは、ファイルをオープンし、ファイルからデータを1バイトずつ、あるいは指定したバイト数分を読み書きすることができます。MemoryStreamクラスは、メモリ上のデータ(具体的にはByte型の配列)から、FileStreamクラスと同様にバイト・データを読み書きします。NetworkStreamクラスは、Windowsソケット・インターフェイスを用いて、ネットワーク上のPCからバイト・データを読み書きします。
例えば、FileStreamクラスを利用し、JPEGファイルからBitmapオブジェクトを作るためのコードは次のようになります。
Stream st = new FileStream("picture.jpg", FileMode.Open);
Bitmap bm = new Bitmap(st);
また例えば、Web上にあるビットマップを読み込み、Bitmapオブジェクトを作るコードは次のように記述できます*3。
WebClient wc = new WebClient();
Stream st = wc.OpenRead("http://www.atmarkit.co.jp/fdotnet/images/fdotnet_m.gif");
Bitmap bm = new Bitmap(st);
*3 WebClientクラス(System.Net名前空間)は、Web上のデータにアクセスするためのクラスです。OpenReadメソッドでは、パラメータで指定されたURLの示すデータを読み取ることができます。このメソッドが返すオブジェクトは、実際にはStreamクラスの派生クラスであるConnectStreamクラス(内部的に利用されるため公開されていない)ですが、OpenReadメソッドはこのオブジェクトをStream型のオブジェクトとして返します。
このように、BitmapクラスのコンストラクタはStream型のパラメータを受け取れるようになっているため、Streamクラスの派生クラスにより提供されるさまざまな種類の入出力デバイスのデータを基にBitmapオブジェクトを作成することができます。
なお、Streamクラスが読み書きする入出力デバイスのデータは、連続したデータの流れということで「ストリーム」と呼ばれます。
処理内容を切り替えるポリモーフィズム
もう少しStreamクラスとBitmapクラスについて考えてみましょう。
Streamクラスにはバイト・データを読み込むためにReadメソッドが用意されています。Readメソッドにより得られるものはバイト・データですが、例えばファイルからの読み出しとネットワークからの読み出しでは、その実際の処理内容は全く異なります。そのためStreamクラスの各派生クラスはReadメソッドをオーバーライドすることによって、各クラス独自の処理内容を提供することになります(オーバーライドについては前回で解説しました)。
一方、Stream型のパラメータを受け取るBitmapクラスのコンストラクタは、パラメータとしてStreamオブジェクトが渡されると、そのReadメソッドを呼び出してビットマップ・データを読み込み、ビットマップを構築します。しかし、Bitmapクラスのコンストラクタは、StreamクラスのReadメソッドを利用しているだけで、Readメソッドがディスクからデータを読み込んでいるのか、ネットワークから読み込んでいるのかをまったく知る必要はありません。知らなくても、各派生クラスで定義されているReadメソッドを正しく実行できます。
図9 StreamクラスのReadメソッドを利用するBitmapクラス
Bitmapクラスのコンストラクタは、StreamクラスのReadメソッドを利用してビットマップ・データを読み込む。実際のReadメソッドの処理内容は、Streamクラスの各派生クラスのReadメソッドで記述されている。
このように、派生クラスでオーバーライドされたメソッドをその基本クラスの型を通じて呼び出すことにより、同じメソッドの呼び出しにもかかわらず実際の処理内容をさまざまに切り替えることができる機能は、オブジェクト指向プログラミングでは「ポリモーフィズム」(Polymorphism、日本語では「多態性」「多様性」など)と呼ばれます。
ポリモーフィズムのおかげで、Bitmapクラスではさまざまな入出力デバイス(ストリーム)に対して共通の方法でアクセスできます。また、Bitmapクラスのコンストラクタが依存しているのはStreamクラスだけです。このため、実際の処理を担当しているStreamクラスの派生クラスを、Bitmapクラスに影響を与えずに差し替えることができます。同様にして、もし将来新たな入出力デバイスが登場したとしても対応可能です。
継承が必須な抽象クラス
Streamクラスには、主要な機能として、バイト・データの読み込み(Readメソッド)に加え、書き込み(Writeメソッド)とシーク(Seekメソッド。読み書きする位置を変更する)を用意しています*4。
*4 ネットワークに対しては、多くの場合連続した読み書きのみが可能で、シークすることができません。StreamクラスにはCanRead、CanWrite、CanSeekプロパティにより、これら3つの機能がサポートされているかどうかを事前にチェックできます。
これらの機能は、データ・ソースとなる入出力デバイスに共通した機能ですが、上述したように、その内部処理はデバイスによって大きく異なります。このため、Streamクラスでは、ほとんどのメソッドについて、最初から派生クラスでオーバーライドされることを前提に定義されています。
このようなクラスは特別に「抽象クラス」と呼ばれ、クラスの定義時には、「class」の前に「abstract修飾子」を付けて宣言します(C#の場合)。VB.NETでは、「Class」の前に「MustInheritキーワード」を付けます。クラスにabstract(あるいはMustInherit)が付いていれば、それは抽象クラスです。
VB.NETのMustInheritというキーワードからも分かるように、抽象クラスのインスタンスを作りたいときは、その抽象クラスから派生した(抽象クラスではない)派生クラスを作る必要があります。抽象クラスをインスタンス化することはできません。
また、派生クラスで必ずオーバーライドする必要のあるメソッド(あるいはプロパティ)は、抽象クラスにおいて「抽象メソッド」として宣言されます。これはC#では「abstract修飾子」、VB.NETではFunctionやSubの前に「MustOverrideキーワード」を付けます。オーバーライドされることが前提ですので、メソッドの中身については記述しません。抽象メソッドを持つクラスは、必ず抽象クラスとなります。
以下のC#のコードは、Streamクラスの定義の一部を示したものです(クラス・ライブラリのソース・コードが公開されているわけではないので正確ではありません)。
図10 抽象クラスとして宣言されているStreamクラス(C#による定義)
抽象クラスにはabstract(VB.NETではMustInherit)が付いている。また、抽象メソッドにはabstract(VB.NETではMustOverride)が付く。抽象メソッドを持つクラスは必ず抽象クラスでなければならない。抽象クラスでは、抽象メソッドでない普通のクラスを記述することも可能。
Streamクラスを継承する(抽象クラスではない)クラスでは、ReadやSeek、Writeなどのすべての抽象メソッドをオーバーライドして、実際の処理を実装しなければなりません。
クラス・ライブラリにはたくさんの抽象クラスが含まれていますが、最初のうちは自分で抽象クラスを定義することはめったにないので、抽象クラスの記述方法についてはこの程度にしておきます。
■クラス・ライブラリで定義されている抽象クラス
ポリモーフィズムと抽象クラスについては、今回のStreamクラスの例だけでは分かりづらかったかもしれませんので、最後に.NET Frameworkのクラス・ライブラリにある代表的な抽象クラスとその派生クラスをいくつか列挙しておきます。リファレンス・マニュアルを調べながら、いろいろ研究してみてください。
抽象クラス | 抽象クラスの派生クラス | クラスの用途 |
---|---|---|
TextReaderクラス (System.IO) |
StreamReaderクラス (System.IO) StringReaderクラス (System.IO) |
ストリームあるいは文字列から、文字データを読み込む |
TextWriterクラス (System.IO) |
StreamWriterクラス (System.IO) StringWriterクラス (System.IO) |
ストリームあるいは文字列に対して、文字データを書き込む |
WebRequestクラス (System.Net) |
FileWebRequestクラス (System.Net) HttpWebRequestクラス (System.Net) |
Web上のリソースに対して送信するリクエストを表すクラス |
WebResponseクラス (System.Net) |
FileWebResponseクラス (System.Net) HttpWebResponseクラス (System.Net) |
Web上のリソースに対して受信するレスポンスを表すクラス |
CommonDialogクラス (System.Windows.Forms) |
ColorDialogクラス (System.Windows.Forms) FileDialogクラス (System.Windows.Forms) FontDialogクラス (System.Windows.Forms) PrintDialogクラス (System.Windows.Forms) |
コモンダイアログ・ボックスを表示するためのクラス |
FileDialogクラス (System.Windows.Forms) |
OpenFileDialogクラス (System.Windows.Forms) SaveFileDialogクラス (System.Windows.Forms) |
コモンダイアログ・ボックスの一種であるファイル・ダイアログ・ボックスを表示するためのクラス |
Menuクラス (System.Windows.Forms) |
ContextMenuクラス (System.Windows.Forms) MainMenuクラス (System.Windows.Forms) MenuItemクラス (System.Windows.Forms) |
Windowsアプリケーションで使用するメニュー |
DbDataAdapterクラス (System.Data.Common) |
OdbcDataAdapterクラス (System.Data.Odbc) OleDbDataAdapterクラス (System.Data.OleDb) SqlDataAdapterクラス (System.Data.SqlClient) |
ADO.NETによりデータベースにアクセスする場合に利用するデータ・アダプタ |
(カッコ内はそのクラスの名前空間)
ちなみに、図3で示したControlクラスの派生クラスの中で、ブルーになっているButtonBaseクラスとListControlクラスは抽象クラスです。
.NET Frameworkのクラス・ライブラリを使いこなすには、継承を使わないとしても(実際に自分で基本クラスや派生クラスを定義しないとしても)知っておくべきことがもう少しあります。ということで次回に続きます。
Copyright© Digital Advantage Corp. All Rights Reserved.