|
||||||||
オブジェクト指向クラスの継承 prev<< | >>next メソッドテーブル 多態性(ポリモーフィズム)
多態性(ポリモーフィズム)はオブジェクト指向の基本となる(であり自然な)動作です。
多態性の動作
まず1つ大きく概要として理解しておくことは、多態性を利用することで、呼び出される側のクラスが変わっても、呼び出し側・メインとなる処理は変わらない構造を作れるということです。
SomeProcessクラスはメインとなる処理を行うクラスで、Figureクラスはなんだか知らないけども図形クラスです。
図形クラスには描画処理であるDrawメソッドが定義されています(virtualです)。
そして、Figureクラスを継承した3つのクラス、Circle, Triangle, Squareがあり、それぞれFigureクラスのDrawメソッドをoverrideしています。
Circle, Triangle, SquareクラスのDrawメソッドは、それぞれ "○","△","□" を描画する処理を実装しています。
Circle, Triangle, SquareのクラスのDrawメソッドは、自分の描画すべき図をコンソールに出力する処理を実装しています。
SomeProcessクラスのMain処理の中は2つに分けられます。
ちなみにAllayListクラスは、オブジェクトをリスト形式で格納するクラスです。格納する際の型は最上位クラスであるSystem.Object(以下、Object)クラス型で格納しますので、 全ての型のオブジェクトを格納することができます。(オブジェクトの上位クラスへの代入互換) ArrayListは、追加されたオブジェクトが何のクラスのインスタンスであるかは知り得ませんし、知る必要もありません。あくまでもObjectクラス型としてインスタンスを格納しているのみです。 実行結果は次のようになります。
SomeProcessクラスでの描画処理の詳細を見てみます。
オブジェクトを上位のクラス型変数に代入しても、インスタンス自体も変数に代入される参照値もなんら変わりはありません。
ただただオブジェクトを使用する側(ここではfigを使う側)がそのオブジェクトをどのように扱えるかが変わるのみです。
そして、継承をうまく使うことにより、上位のオブジェクト(だと思って)に対してメソッドをコールしても、
実際はそこに格納されている参照先のオブジェクトのメソッドが呼び出される。という動作をさせることができます。
また、サンプルソースではFigureクラスのDrawメソッドにも"Figure->Draw"が出力されるようにコーディングされていますが、 出力結果には出力されていません。後述の 上位クラスのメソッドの存在の意味は? でお話しますが、なぜここで出力されないのかを しっかりと理解してください。FigureクラスのDrawメソッドは実際どこからも呼び出されていません。 (Figureクラスを継承したくラスを作成し、Drawメソッドをoverrideしなかった場合は、"Figure->Draw"が出力されますが)
最後に、ここではSomeProcessクラスは上位クラスであるFigureクラスのDrawメソッドのコールで「描画をする」ことさえ指示すれば、あとは自然に適切な「描画」が行われる。
という説明を主としています。
実際の開発で43〜45行目のように、ベタでCircleなどのクラスをNewしていては意味がありません。
実際の開発では、Circleな, TriangleなどのインスタンスはSomeProcessクラスに引数で渡されてきたり、クラス定義をXMLなどに行っておき、
何らかの条件から機械的にクラス名を取得しインスタンスを生成(クラス名からインスタンスを生成)するなどし、
SomeProcessクラス内にCircle, Triangleなどのクラスは存在しないような設計をします。
メイン処理フロー側のSomeProcessクラスは、Figureオブジェクト(実際のインスタンスはFigureクラスを継承したクラスのインスタンスであり何のクラスのインスタンスかはわからない)
に対してしか意識していないことがポイントです。実際に何の図形が出力されるかは別として、「図形を描画する」ということを行い
次の処理に進むという、共通的なメイン処理を作ることができます。そして、Figureクラスを継承したクラスは、出力する図形に関してしか
意識する必要はありません。
上位クラスのメソッドの存在の意味は?
ところで、多態性の動作 の サンプルソース1 内にある、FigureクラスのDrawメソッドは、
結局どこからも呼ばれていません(Figureクラス型変数の参照先のインスタンスがFigureクラスのインスタンスではなかった為)。
○,△,□は出力されましたが、"Figure->Draw" は出力されませんでした。
今回サンプルとしてあげた3つのクラス Circle, Triangle, Squareクラス以外に、Figureクラスを継承したクラスが存在し、
そのクラスがDrawメソッドをオーバーライドしなかった場合や、Figureクラス自体をインスタンス化した場合などは、
"Figure->Draw" は出力されます。
つまり、今回Figureクラスは次のソースで十分といえます。
Drawメソッドは、中にソースが1行も存在しません。しかし「FigureクラスのDrawメソッド」が無かったら
サンプルソース1の49行目のように、それぞれの図形描画を1文で表すことはできません。
空の(仮想)メソッドに非常に重要な役割があることがわかると思います。
仮想メソッドの場合、メソッドの中が空であってもメソッドそのものの存在自体に意味があります。
ちなみに先にサンプルであげた サンプルソース1 のvirtualなFigure.Drawの処理は確かに
動作させる必要はありませんでした(というか動作しない方がよい)。
しかし、図形出力の際に前準備が必ず必要で、その処理を上位クラスのDrawメソッドが実装してたとしたらどうでしょう。
(共通な処理を上位のメソッドが実装し、virtual定義ということはよくあるパターンです)
CircleクラスのインスタンスのDrawメソッドをコール(31,32行目)した際に動作するメソッドは、
インスタンスへの参照を格納している変数の型が、CircleであってもFigureであっても Circle.Draw です。
Firgure.Drawは31,32行目の呼び出しから直接呼び出されることはありません(仮想メソッドとメソッドのオーバーライド を参照)。
したがって、今描画の前処理として必須としている 5行目の処理 を行うためには 13行目の base.Draw のコールは必須となります。つまり、
このように、オーバーライドしたメソッドから上位のメソッドをコールするということは、処理の追加になります。
上位のメソッドをコールしないということは完全にメソッドの上書き(書き換え)になります。
開発の二分化
先に(多態性の動作)に記したような発想でクラスを組み立てることで、
メイン処理側と、個別処理を完全に分離させることができるようになります。
メイン処理となるSomeProcessクラスと、「描画をする」という抽象的で中身のない(定義だけで実際には処理をしない)Figureクラスを作成するA部。
そして、A部から提供されるFigureクラスを元に、それらを派生して「実際の描画を行う処理」を作成するB部。
A部では「描画をする」という行為がある。という前提において処理を完結させることができます(サンプルソース49行目のように、fig.Draw(); でおしまい)。
しかし、A部だけの完結では処理がほとんど出来上がったようなものですが、実際の描画処理がない未完成品です。
Figureクラスを継承してどのような図形を描画するクラスが作成させれていくかをA部はわかりませんが、A部としてはこれで完成です。
B部開発者はFigureクラスを継承し、実際に必要な図形クラスを作成します。ここでは逆にA部が何をしているのかを意識する必要はありません。
非常に簡単な例ではありますが、各種フレームワークはこのような形を基本に形成されています。
おおまかにいってしまうと、A部側はフレームワークなどの特定の業務に特化しない開発者に利用されるクラスの開発で、 B部側は、フレームワークを利用した業務アプリ開発。という区分けになります。
A部,B部と簡単にいっていますが、この位置関係のレイヤーは捕らえ方によって変わりますし、
自分たちでの定義(意識)も必要となってきます。
例えば、JavaAPI,.NET Framework はもちろんA部に該当しますが、
サードパーティが提供するフレームワークもA部であり、開発プロジェクト内で専用のアプリケーションフレームワークを作成すれば
それもまたA部となります。
この多態性の利用で、最終実装であるB部がなくとも全体の処理を組み上げることができる(半完成品)ことにより、 メインフローの共通化・標準化を推し進めることが用意になります。また、B部の開発は、A部が提供する機能を 学習する必要はありますが、システムの全体的な動作などは気にかけず、自分の開発すべき箇所にのみ注力して 開発を進めることができます。
クラスの継承 prev<< | >>next メソッドテーブル |
|
|||||||