| ブログ内検索: | |
逆に言うと、「壁」にひっかかっていることに気づかず、壁の手前でかなり無駄な時間を過ごしてしまったわけです。
中上級者にとっては当たり前すぎて今更な知識ですが、私の周りを見ていると、この壁のところにひっかかって無駄な時間をすごしてしまっている初学者の方がけっこういらっしゃるようですので、その壁を突破する鍵となった知識を、図解してみることにしました。
以下の図は、家の絵を描画するプログラムの動作イメージです。
各オブジェクトのところにある「描く」メソッドは、『そのオブジェクトに対して「描く」メソッドを呼び出すことができる』という意味です。メソッドの定義自体は、下図のように、その所属クラスで定義されています。
オブジェクトとクラスを一つの図に詰め込むと、以下のようになります。
以下は、このプログラムの動作を示す擬似コードです。
「配列1」には「三角1」、「長方形1」、「長方形2」、「線1」の4つのオブジェクトが格納されており、ループを回して、それぞれのオブジェクトの「描く()」メソッドが順に呼び出されていきます。
「三角1」の「描く()」メソッドを呼び出すと、三角オブジェクトの「描く()」メソッドが実行されます。これは三角形を描画するメソッドです。
また、「長方形1」の「描く()」メソッドを呼び出すと、長方形オブジェクトの「描く()」メソッドが実行されます。これは長方形を描画するメソッドです。
この二つのメソッドは別のプログラムコードですが、どちらも「描く()」というメソッド名で呼び出すことが出来るようになっているのです。
このように、「異なる実装のメソッドを同じメソッド名で呼び出すこと」を、ポリモルフィズムと言います。
しかし、ポリモルフィズムの何が嬉しいんでしょうか?
それを見るために、パワーポイントにおける「図形のグループ化」を使ったプログラム構造を考えてみます。パワーポイントの「グループ化」とは、複数の図形をまとめて一つの図形として扱う機能で、ここではグループ化された図形を「複合図形」というクラスで実装してみます。オブジェクト構造と動作イメージは以下のようになります。
クラスとインスタンスの関係は以下のようになります。
クラスとオブジェクトの情報を一つの図に詰め込むと以下のようになります。
以下はその擬似コードです。
複合図形オブジェクトは、自分の子オブジェクトを格納した配列を持っています。
複合図形オブジェクトの「描く()」メソッドは、その配列をループで回して、全ての子オブジェクトの「描く()」メソッドを実行する、という実装になっています。
複合図形オブジェクトの子オブジェクトに別の複合図形オブジェクトが含まれている場合、ツリー構造の末端まで、再帰的に「描く()」メソッドが実行されていきます。
このようなクラス設計構造を「COMPOSITEパターン」と呼びます。オブジェクト指向設計におけるデザインパターンの1つです。
クラス定義から含めて、全部をRubyで書くと以下のようになります。
先頭の二行である「# encoding: Windows-31J」と「Encoding.default_external = 'Windows-31J'」は、日本語文字コードの指定です。文字化けしないようにするためのものです。(ここではWindowsで実行することを想定している)
クラス名の先頭に「C」がついているのは、Rubyのクラス名は大文字の英文字で始まらなければいけない、というルールがあるからです。
これを実行すると、次のような出力画面になります。
次に、同じプログラムをJavaで書いてみます。
まず、Javaだとクラス名がそのままクラスファイルになるため、クラス名に日本語を使うと面倒なことが発生することがあるので、ここでは識別子は全部英文字にしてみました。
また、Javaの場合、下図のように「Picture」インタフェースを定義し、全ての図形クラスはPictureインタフェースを実装すると宣言してから、クラス定義しなければなりません。
コードは以下のようになります。
rubyの場合、単に「描く」メソッドを持っているオブジェクトを配列に格納してループを回せばそれでポリモルフィックなメソッド呼び出しが出来ますが、Javaだと配列に格納された全てのオブジェクトが「draw()」メソッドを持っていることを、ソースコード内で「保証」するような書き方をしないと、コンパイルエラーになります。(「バグがないこと」を「保証」しているわけではない)
Javaだと、「draw()」メソッドを持っていることを「保証」するやり方は二通りあります。
一つがインタフェースを使うやり方です。この例がそれで、「interface」宣言を使って「draw()」メソッドを持つ「Picture」インタフェースを定義し、各クラスが「implements Picture」という宣言をすることで、そのインタフェースを「実装」していることを明示しています。
もう一つが「継承」を使うやり方で、親クラスで「draw()」メソッドを定義し、その親クラスを各図形クラスが「継承」し、それぞれの子クラスで「draw()」メソッドの実装を上書きすることで、同様のことができます。
このように、Java言語だと「インタフェース」もしくは「継承」を使わないとポリモルフィズムが実装できませんので、それらがポリモルフィズムと一体不可分であるかのように錯覚してしまう方がときどきいらっしゃいますが、「インタフェース」も「継承」も使わずにポリモルフィズムを使ったクラス設計の出来るrubyのような言語を見て分かるとおり、それらはポリモルフィズムの本質とは直接関係があるわけではありません。
「COMPOSITE」パターンのキモは、「部品」と「部品の容器」を同じものとして扱うことです。
たとえば、ファイルとフォルダをどちらも「削除()」というメソッドで削除できるとします。
すると、ユーザのファイル削除操作の擬似コードは、以下のように記述することが出来ます。
ポリモルフィズムを使わずにこれを実装しようとすると、以下のように、いちいち変数ファイル1に格納されているのがファイルであるかフォルダであるか、そのオブジェクトの種別を判定して、それによって処理を条件分岐させるという実装になります。
フォルダとファイルの例だとオブジェクトの種類は2つしかありませんので、さほど煩雑にはなりません。
また、プログラムの規模が小さい場合、少々オブジェクトの種類が増えたところで、たいして煩雑にはなりません。
しかしながら、オブジェクトの種類が多くなるにつれ、また、プログラムの規模が大きくなるにつれ、この手の煩雑さはどんどん増加していき、可読性や保守性が落ちていきますので、それに対処するためにポリモルフィズムを使ったプログラミングの必要性が高まっていくのです。
前半(無料)はここで終わりです。
後半を読むにはメダル(約100円分)の付与が必要です。
後半では、いよいよ『継承を効果的に使って処理を記述する技法』を図解します。
「継承の概念と仕組みを理解すること」と、「『継承を効果的に使って処理を記述する技法』を習得すること」は別の話です。単に継承の概念と仕組みを理解しただけではダメで、「『継承を効果的に使って処理を記述する技法』を習得しないと、オブジェクト指向プログラミング技術はろくに身につきません。
後半の具体的な内容としては、(1)継承を使って処理を記述するためのクラス設計やオブジェクト構造の動作メカニズムを図解し、「継承を使って処理を記述することの、なにが嬉しいのか?」を説明します。
その次に、(2)「継承を使ったクラス設計の限界」を示し、その問題点を解決するためのクラス設計技法を図解します。
さらにその次に、(3)継承を使った処理の記述も、その限界に突き当たったときの代替案も、どちらも使わない方がよい場合について考察し、その見極め方について解説します。
最後に(4)この記事で使用した全てのサンプルプログラムのソースコードのテキストデータは、後半部分の末尾に貼り付けてありますので、実際に自分で動かして試してみることが出来ます。
この記事にメダルを付与する手順はこちらです。
■ランキング ■通算ランキング ■メダルって?■メダル付与 ■コメント&トラックバック一覧 ■ 2人、コメント2人、スタンプ2人、メダル200ポイント
|
|
最近書いた記事一覧 |
ブログ内検索:
|