第1章 初めてのデザインパターン
Authors:Agata Toshitaka
本特集は初心者の方にも理解しやすいように心がけていますが、デザインパターンはオブジェクト指向の理解の上に成り立っています。
デザインパターンではオブジェクト指向の3大要素(カプセル化、継承、ポリモフィズム)のうち、特に「継承」「ポリモフィズム」を効果的に利用したものが多いです。
ここではこの2つについて復習してみましょう。
すでに定義されたクラスの機能を引き継いで、新しいクラスを定義することを継承と言います。
Java言語ではサブクラスでスーパークラスを「extends」することで継承を実現します。
継承をうまく利用することで、サブクラス間で共通の部分をスーパークラスに引き上げることができます(図1)。
第3章で解説するテンプレートメソッドパターンは、継承の特徴をうまく利用した典型的なパターンです。
図1 継承
ポリモフィズムは「多態性」と呼ばれます。 …しかし、多態性という言葉ではまったく意味がわかりません。
少なくとも筆者はそうでした。ポリモフィズムを一言で説明するのはたいへん難しいです。
しかし、多くのデザインパターンではポリモフィズムが多用されていますので、デザインパターンの理解のためにもポリモフィズムは避けては通れません。
Javaでポリモフィズムを実現する方法としては、インタフェースを使うパターンと、抽象メソッドを定義し抽象クラスで実現するパターンがあります。
ここではインタフェースを題材に、「例」からポリモフィズムを理解していきます。
まずここに3つのクラスがあります。
人を表すPersonクラス、チームを表すTeamクラス、ラベルの印刷を行うLabelPrinterクラスです。
PersonクラスとTeamクラスは名前を持っていて,getName()メソッドで取得することができます。
また、LabelPrinterクラスは、printLabel()メソッドで画面にラベルを出力します。
◎ポリモフィズムを使わない場合
ポリモフィズムを使わない場合、LabelPrinterで出力したいクラスが増えるたびに、そのクラス専用のprintLabel()メソッドを増やしていく必要があり、たいへん不便です(図2)。
図2 ポリモフィズムを使わない場合
◎ポリモフィズムを使った場合
そこで名前を取得する共通のインタフェースとしてNamableインタフェースを定義します(図3)。
図3 ポリモフィズム(インタフェース)を使った場合
このインタフェースではgetName()メソッドを定義します。PersonクラスとTeamクラスはNamableインタフェースを実装します。
これで、PersonクラスとTeamクラスのオブジェクトは、「Namableのオブジェクトとして振る舞える」忍者のようなオブジェクトになりました。
そこで、LabelPrinterのprintLabe()メソッドをNamableインタフェースを受け取るように書き換えます。
これにより、PersonだろうとTeamだろうと、「Namableインタフェースを実装するクラス」なら、どんなものでも印刷できるprintLabel()メソッドができあがりました。
LabelPrinterのprintLabel()メソッドでは、NamableオブジェクトのgetName()メソッドを呼び出しています。
ここでの
getName()メソッドの処理内容はNamableオブジェクトがもともとなんのクラスだったのかによって振る舞いが変わります。
振る舞いが変わってくれからこそ、printLabel()メソッドは1つですむのです。
このもともとなんのクラスだったのかということによって振る舞いが変わることが、「ポリモフィズム」の本質です。
ポリモフィズムの実現に、インタフェース、抽象クラスのどちらを使った場合も、「振る舞いがかわる」という点では同じです。
参考までに、図4に抽象クラスを使った場合のPersonクラスとTeamクラスをご紹介します。ポリモフィズムとしての効果はインタフェースの場合とまったく同じです(COLUMN参照)。
COLUMN 抽象クラス・インタフェースどっちの料理ショー
抽象クラスとインタフェースにはどちらも似たような効果があります。
どちらを使ったらよいか、最初のうちは筆者もかなり悩みました。
筆者の意見としては、「
テンプレートメソッドパターン(第3章参考)のように共通的な実装があり、サブクラスで変更したい部分のみを抽出して抽象メソッドにした場合、抽象クラスを使う」。
それ以外で「クラス間の共通の振る舞いを定義したい場合、インタフェースを使う」というものです。
今回の例の「Namable」では、「getName」という共通の振る舞いを定義したいだけですので「インタフェース」ということになります。
抽象クラスだと多重継承ができませんので、インタフェースのほうが有利な場合が多いということも知っておくと便利です。
また、あとから抽象クラスからインタフェースを抽出したり、インタフェースを抽象クラスに戻したりすることもよくありますので、難しく考え過ぎず、柔軟に適応していっても良いと思います。