いまさら聞けないJavaによるオブジェクト指向の常識プログラマーの常識をJavaで身につける(11)(4/5 ページ)

» 2008年05月08日 00時00分 公開
[伊賀敏樹, 山本耕司株式会社 NTTデータ ビジネスブレインズ]

「メッセージ・パッシング」、Javaではメソッド呼び出し

 ラーメン屋では、「ラーメン屋のお客」が「ラーメン料理人」にラーメンを注文することにより、「ラーメン」を受け取ります。これをオブジェクト指向に当てはめて考えると、複数のオブジェクトが協調して動作することにより、目的を果たしているといえます。

 複数のオブジェクトが協調して動作するには、オブジェクトからオブジェクトに「何か」を伝えなくてはなりません。この、オブジェクトに伝える「何か」をメッセージ、オブジェクトにメッセージを伝えることを「メッセージ・パッシング」と呼びます。

 ラーメン屋の例でいうと、「ラーメン屋のお客」から「ラーメン料理人」にラーメンを注文することが、まさにメッセージ・パッシングの1つに相当します。前述のプログラムでは、以下のように表現されています。

        // ラーメン料理人にラーメンを注文する
        final Ramen ramen = cook.acceptRamenOrder();

 Java言語上では、メッセージ・パッシングはメソッドの呼び出しとして表現されます。このとき、メッセージを送るオブジェクトが、メッセージを受け取るオブジェクトのメソッドを呼び出している点に注意してください。

コラム 「同じ言葉でも意味が違う“インターフェイス”」

オブジェクト指向の基本概念を説明する際に、あるオブジェクトが受け取ることができるメッセージをまとめて「インターフェイス」と呼ぶことがあります。この「インターフェイス」は、Java言語仕様上の「インターフェイス」とは意味が異なる点に注意してください。


「カプセル化」、Javaではアクセス修飾子などで実現

 ここで、ラーメン屋のお客が勝手に、ラーメンのスープを「ぜんざい」に変えようと試みます。

図7 Eclipseでアクセス修飾によりコンパイルエラーが発生する例 図7 Eclipseでアクセス修飾によりコンパイルエラーが発生する例 

 ところが、RamenオブジェクトのfSoupフィールドの値を「ぜんざい」に変えようとしても、コンパイルエラーが発生して変更できません。これは、fSoupフィールドがprivateアクセス修飾されているからです。また、fSoupフィールドを変更するためのメソッドも提供されていないため、ラーメンのスープを勝手に変えることはできません。

 また、そもそもfSoupがprivateでアクセス修飾されているので、外部からはスープがfSoupとして表現されていることを知る必要もありません。getSoupメソッドを使ってスープを取得する限り、ラーメン・クラスの内部でスープの表現方法が変わっても影響を受けません。

 このように、公開されている(オブジェクト指向でいう)インターフェイスを通してオブジェクトにアクセスすることにより、オブジェクトのデータが誤って変更されることを防いだり、データの内部表現の変更による影響を無視できるような性質を、「カプセル化」と呼びます。

 Java言語では、アクセス修飾子(public、protected、private)やパッケージなどを用いてカプセル化を実現しています。

「多態性」、同一のメッセージで異なる振る舞い

 いままで登場した「ラーメン」「チャーシューメン」「ねぎラーメン」には、ラーメンを表示するという目的の類似したメソッドがあります。ここでは、IRamenインターフェイスを導出して、ラーメンを表示するという共通したメソッドを持たせることにします。

IRamen.java
/**
* ラーメン・インターフェイス
*/

public interface IRamen {
    /**
    * ラーメンを表示
    */

    void show();
}

 「ラーメン・クラス」を以下のように書き変えます。

Ramen.java
public class Ramen implements IRamen {

……(略)……

    /**
    * ラーメンを表示
    */

    public void show() {
        System.out.println("ラーメンを表示");
        System.out.println(" スープ : " + getSoup());
    }

……(略)……

}

 同じように、チャーシューメン・クラス(CharSiuRamen)とねぎラーメン・クラス(LeekRamen)の「ラーメンを表示」メソッドのメソッド名も変更します。

CharSiuRamen.javaとLeekRamen.javaのメソッド名を変更※注意:JDK 1.5以上の場合には、@Overrideアノテーションを付けることもできます(付けなくてもよいです)
    /**
    * ラーメンを表示
    */

    @Override
    public void show() {
        System.out.println("ラーメンを表示");
        System.out.println(" トッピング: " + getTopping());
        System.out.println(" スープ : " + getSoup());
    }

 IRamenインターフェイスを導入したことにより、ラーメン屋のお客は、ラーメンの種類にかかわらず、IRamenインターフェイスを通じてラーメンのインスタンスを扱うことができるようになります。

Customer.java
public class Customer {
    public static void main(final String[] args) {
        // ラーメン料理人・インスタンスを作成する
        final RamenCook cook = new RamenCook();

        // 出来上がったラーメンはIRamenインターフェイスを
        // 通じて扱うことができるようになる

        final IRamen ramen = cook.acceptCharSiuRamenOrder();

        // 出来上がったラーメンを表示する
        ramen.show();
    }
}

 この変更によって、ラーメンの種類ごとにラーメンの表示メソッド名を変更する必要がなくなりました。注文メソッドをacceptLeekRamenOrderに変えてみて、コンパイルおよび実行ができることを確認してみましょう。

 このように、異なる種類のクラスからなるオブジェクトが、同一のメッセージを受け取れることを保証しながら、そのメッセージに対してオブジェクトごとに異なる振る舞いができる性質を、「多態性」と呼びます。

instanceof演算子を使うと、どのクラスか分かる!

 一方で、いま扱っているインスタンスが、どのクラスのインスタンスであるのかを調べる必要が出てくることがあります。このような場合には、instanceof演算子を利用して、どのクラスのインスタンスであるかを調べることができます。

    System.out.println("インスタンス判定");
    if (ramen instanceof LeekRamen) {
        System.out.println(" ねぎラーメン・インスタンス");
    } else if (ramen instanceof CharSiuRamen) {
        System.out.println(" チャーシューメン・インスタンス");
    } else if (ramen instanceof Ramen) {
        System.out.println(" ラーメン・インスタンス");
    }

 最後の次ページでは、Java APIの「裏技」について解説し、難しいオブジェクト指向を習得するためのヒントを教えます。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。