更新日: 2010 年 3 月 26 日

執筆者: エディフィストラーニング株式会社 矢嶋 聡

この記事は、「MSDN プログラミング シリーズ」として発行している技術書籍「ステップアップ Visual Basic 2010 ~開発者がもう一歩上達するための必読アドバイス」(日経 BP 社刊) を基に先進的なテクニックを紹介しています。


目次

  1. はじめに
  2. 親子関係とクラス継承との違いは?
  3. クラス継承の基本的な特徴
  4. クラス継承の利用例

1. はじめに

Visual Basic などのオブジェクト指向プログラミング言語では、既存のプログラムの再利用性を高める重要な仕組みとして「クラス継承」があります。ご存知の通り、この「クラス継承」を使用すれば、あるクラス定義から継承することを宣言して、差分の情報 (フィールド、メソッドなど) を追記するだけで、継承元の機能も備わった新しいクラスを定義することできます。

一般に、このような継承の際に元となるクラスは「基本クラス」と呼ばれ、継承によって新規に定義されたクラスは「派生クラス」と呼ばれており、この他にもいくつか呼び名があります。場合によっては、このような「クラス継承」を親子関係になぞらえて、元になるクラスのことを「親クラス」、派生したクラスのことを「子クラス」と呼ぶ場合もあります。たしかに、親子関係にたとえる表現は、直観的には受け入れやすいでしょう。(実際、筆者もセミナーの開催時には、初心者にも馴染みやすくするため、あえて「基本クラス」のことを「親クラス」と呼ぶことがあります。)

しかし、厳密に言うと「基本クラス」と「派生クラス」の関係は親子関係とは言い難いところがあります。そこで今回は、「クラス継承」の特徴について、改めて取り上げてみることにしましょう。

ページのトップへ


2. 親子関係とクラス継承との違いは?

まず、「親子関係」という言葉自体も曖昧なので、この言葉の意味を考えてみましょう。一般の生物の親子関係では、子供には 2 人の親がいますが、ここでは 1 つの「基本クラス」と 1 つの「派生クラス」との関係を考えるため、一方の親 (父または母) と子との関係に着目します。まず受け継ぐ遺伝子の面からいうと、次のことが言えるでしょう。

  1. 1) 親から 半分の遺伝子を受け継ぐ (= 親から 100% 受け継ぐわけではなく、一部を受け継ぐ)

また、あまり生物としての親子の意味が現われない場合でも、「親子関係」という用語を用いる場合もあります。例えば、以下の例 1 のような XML データでは、外側のブロックを形成する要素のことを親要素と呼び、内側の各構成要素を子要素と呼ぶことがあります。

例 1. XML データの親子関係
Visual Basic
<Product>		←親要素のブロック
  <Id>100</Id>		←子要素
  <Name>Orange</Name>
  <Price>105</Price>
</Product>
 

この例の親子関係には、次の性質が読み取れます。

  1. 2) 親のほうが、子よりも大きい (= 親のほうが多くの情報を含んでいる)
  2. 3) 1 つの親要素の中に、複数の子要素が含まれる (= 子は包含されている)

一般的に 2) や 3) の性質がある関係は、親子関係として自然に受け入れられるでしょう。これに該当する同様の例としては、大きな親ウィンドウの中に埋め込まれた、子ウィンドウなども当てはまります。(ただし、厳密には、子ウィンドウは親ウィンドウのいつくかの属性を受け継ぐことはできますが。)

以上の 1) から 3) が、一般に「親子関係」と呼ぶ場合の基本的な性質でしょう。しかし、「クラス継承」の場合は、上記の 1) から 3) のいずれも該当しません。

「クラス継承」では、「基本クラス」に定義したものすべて (100%) が「派生クラス」に継承されるため、1) は当てはまりません。また、「派生クラス」へは常に「基本クラス」の 100% が引き継がれ、さらに構成メンバーを「派生クラス」に追加することもできるので、むしろ「派生クラス」のほうが大きくなります。よって、2) も当てはまりません。さらに、このような大小関係があることから、3) のような親要素の中に子要素が包含されるという関係でもありません。

ページのトップへ


3. クラス継承の基本的な特徴

上記では、「親子関係」との違いという観点から「クラス継承」の特徴を見ましたが、ここで改めて「クラス継承」の基本的な特徴をまとめてみましょう。

まず前項で触れた「クラス継承」の特徴を端的に表すと、「拡張」であると言えます。例えば、「基本クラス」に 3 つの構成メンバーが定義されていれば、それら 3 つは「派生クラス」へ全部引き継がれます。さらに、必要であれば「派生クラス」には 3 つの構成メンバーのほかに追加することができ、結果として、「基本クラス」が提供する機能が拡張されることになります。

また、このような「クラス継承」を通じて拡張を行う場合、一般的には「派生クラス」のほうがより限定した特定の用途に使用されます。

例えば、.NET Framework クラス ライブラリには、特定の用途に限定されない汎用的なフォーム (ウィンドウ) にあたる Form クラスが用意されています。また、この Form クラスから継承した派生クラスとして PrintPreviewDialog クラスがあります。PrintPreviewDialog クラスは、フォーム (ウィンドウ) であるために、Form クラスからすべての実装を受け継いでいます。さらに拡張されて、印刷のプレビュー機能を提供します。つまり、汎用的な Form クラスに対して、派生クラスである PrintPreviewDialog クラスは、プレビューという特化された機能を備えています。

「基本クラス」と「派生クラス」には、上記のような関係があり、継承の系譜を派生クラスの方向にどんどん進むと、より特定の用途に使われるようになります。このような特徴は「特化」と呼ばれています。一方、継承の系譜を基本クラスの方向に向かってどんどん遡ると、より汎用的なクラスになります。このような特徴は「汎化」と呼ばれています。

また、このような「汎化」や「特化」が成立する関係では、「Is-a の関係」も成立します。「Is-a の関係」とは、A と B との関係において「A は B である (A は B の一種である) 」と言える関係です。つまり、派生クラスのオブジェクトは、基本クラスの一種です。たとえば、前述のプレビュー機能付きフォームも、フォームである (フォームの 1 種である) ことには違いないので、PrintPreviewDialog と Form との間には「Is-a の関係」が設立します。

このように「クラス継承」には、その特徴として、「拡張」、「汎化」や「特化」、また「Is-a の関係」という側面を持ちます。これらは、一般に言う親子関係とは異質のものです。今後、クラス設計の際に、2 つのクラス候補が継承関係にあるか否かを見極める場合には、これらの特徴を思い出してください。

基本的には、2 つのクラスの関係が、次のような文章で表現できるのであれば、「クラス継承」が成立するということができます。「クラス継承」を行う際の目安にしてみてください。(以下の 2 つの項目は同じ意味です)

Root クラスから継承し、Sub1 クラスを派生クラスとするのならば、

  • Sub1 は Root の一種である
  • Sub1 はあたかも Root のように扱うことができる

ページのトップへ


4. クラス継承の利用例

では、「クラス継承」を活かした例を確認しましょう。ここでは、クラスを新規に定義するのではなく、既存のクラス ライブラリの中で、「クラス継承」の関係にある「基本クラス」と「派生クラス」を利用した例を示します。(以下のコードで使用する Windows フォーム上では、Button コントロールと PictureBox コントロールを既定の名前のまま 1 つずつ貼り付けてあることが前提です。)

例 2. 「クラス継承の活用」~ストリームからビットマップを読み取る~

Private Sub Button1_Click(...) Handles Button1.Click                   ←[1]

Dim stream1 As New System.IO.FileStream(                          ←[2]
"C:\Users\Public\Pictures\Sample Pictures\Tulips.jpg",
System.IO.FileMode.Open, System.IO.FileAccess.Read)

ShowPicture(stream1) ←[3]

End Sub

Private Sub ShowPicture(ByVal st As System.IO.Stream)          ←[4]
Dim bmp = Bitmap.FromStream(st)                             ←[5]
Dim gr = Graphics.FromImage(bmp)
gr.DrawString("Sample Data",
SystemFonts.CaptionFont, Brushes.Purple,
New PointF(10, 10))
PictureBox1.Image = bmp
End Sub

例 2 の [1] は、ボタンの Click イベント ハンドラーです。このサンプルでは、フォーム上のボタンをクリックすると、イメージ ファイルを読み込み、そのイメージ ファイルに「Sample Data」というタイトルを付加して、フォームの PictureBox コントロールにそのイメージ データを表示します (図 1 参照)。この例には「クラス継承」における「汎化」と「特化」を生かし、ソース コードの再利用性を向上させた部分があります。

図 1. サンプル コードの実行結果

ボタンがクリックされると [1] のイベント ハンドラーが実行され、[2] から続く 3 行では、FileStream オブジェクトが作成されます。FileStream オブジェクトは、1 つのファイルを操作するためのオブジェクトです。そして [3] では、この FileStream オブジェクト (変数 steam1) を引数として、ShowPicture メソッドを呼び出しています。

一方、呼び出された [4] の ShowPicture メソッドでは、引数 st に FileStream オブジェクトを受け取り、[5] のように受け取った FileStream オブジェクト (JPEG ファイル)から、メモリ上に Bitmap オブジェクトを作成しています。

ここで注目すべき点は、[4] の引数の型が Stream クラスである点 (太字部分) です。実は Stream クラスと FileStream クラスとは「クラス継承」の関係があり、Stream クラスから派生したのが FileStream クラスです。FileStream オブジェクトは Stream オブジェクトの一種なので、あたかも Stream オブジェクトのように利用できます。よって、問題無く引数として渡すことができます。そして、[4] の引数の型を FileStream クラスとせずに、より「汎化」された Stream クラスを用いることで、この [4] のメソッドの再利用性が高まっているのです。というのは、仮に [4] の引数の型を「FileStream」と特化してしまうと、このメソッドはファイルとして保存されていたイメージ ファイルのみを表示するという特定の状況でしか利用できません。しかし、黄色の部分のように引数の型を Stream クラスと定義することで、Stream クラスから派生したどんなクラスのオブジェクトでも、引数として受け取ることができるのです。

Stream クラスの派生クラスには、ファイル データを読み込む FileStream クラスだけでなく、ネットワークからデータを読み込む派生クラスもあります。そのような派生クラスのオブジェクトを引数に渡せば、[4] のメソッドを一切書き変えずして、ネットワークからダウンロードしたイメージ データを表示することもできます。例えば、例 2 の [1] のイベント ハンドラーを次の例 3 のように書き換えることもできます。この場合も、例 2 の ShowPicture メソッドを書き変える必要がありません。

例 3. HTTP を介して、イメージ データを取得する
Visual Basic
Private Sub Button1_Click( ... ) Handles Button1.Click

    Dim request = System.Net.HttpWebRequest.Create(		←[1]
        "http://localhost:8080/TestWeb01/Tulips.jpg")
    Dim response = request.GetResponse()
    Dim stream2 = response.GetResponseStream()			←[2]
    MsgBox(stream2.GetType().Name)				←[3]
    MsgBox(TypeOf stream2 Is System.IO.Stream)			←[4]

    ShowPicture(stream2)

End Sub
 

この例では、Web 上の公開された Tulips.jpg ファイルを HTTP 経由でダウンロードするために、HttpWebRequest オブジェクトを作成しています。そして、この HTTP の応答としてデータをダウンロードする際に利用するストリーム オブジェクトを取得しているのが、[2] です (変数 stream2)。その後は試験的に、このストリーム オブジェクトの型と継承元を調べています。[3] では、「ConnectStream」と表示されます。また [4] では、このストリーム オブジェクトが Stream クラスの一種なので、もちろん True と表示されます。このように、このストリーム オブジェクトは、「基本クラス」である Stream クラスから継承した派生クラスのオブジェクトであり、HTTP 経由でデータをダウンロードするという特化した実装を持ちます。

これも前回の「インターフェイスの使いどころ」で取り上げた「多態性」を実現する 1 つの方法です。

なお、ここではフォーム クラスという限られた範囲でのサンプル コードを取り上げましたが、このような引数のやり取りは、複数のクラスをまたぐようなデータのやり取りでもあり得ます。この仕組みをうまく利用することで、クラス連携やアプリケーション間の連携の組み合わせも柔軟に変えることができるようになり、再利用性が向上するでしょう。

いずれにしても、例 2 の [4] のように、メソッドでデータを受け取る場合には、もともと受け取りたい目的のデータのクラスが A であった場合でも、A を引数の型にするのではなく、A の基本クラスが引数の型として利用できるか否か考えてみてください。継承を「親子関係」と捉えるのではなく、こうした「汎化」・「特化」として普段から捉えておくことで、活用場合は意外とたくさんあることに気づくはずです。


Code Recipe .NET Framework デベロッパー センター

ページのトップへ