Go to Previous Page Go to Contents Go to Java Page Go to Next Page
Second Step of Java
 
 

人間 vs. コンピュータ

 
 

人がゲームをする

 
 

今まで Player クラスはコンピュータがゲームを行うということを前提にしていました。しかし、Judge クラスから見れば、getHand 関数さえ実装してあればコンピュータがゲームを行うのでも、人間がゲームを行うのでもまったく変わりはありません。前節でも説明しましたが、機能さえあれば、どのように実装を行っていても同じように扱うことができます。

それでは、Player クラスをもう少し汎用化してみましょう。Player クラスに必要なのは public な関数である getHand 関数、getName 関数と valueOf 関数ですが、重要なのは getHand 関数です。valueOf 関数は static 関数で手を引数にしていますが、static な関数はやめて、Player オブジェクトが自分が出した手を覚えておいてそれを出力するような関数にしてみましょう。

結局、Player クラスは 2 つの関数だけを定義したものになりました。このように、機能だけを取り出して定義したものがインタフェースです。Player クラスもインタフェースとして定義しなおしてみましょう。 インタフェースには public な関数の定義と static な定数の定義だけ行うことができます。

インタフェースを使用することで、機能の定義と実装を完全に分けることができます。インタフェースを使用するオブジェクトは機能がどのように実装しているかをまったく気にせずに、インタフェースで定義された機能を使用することができます。いうなれば、インタフェースを使うことで実装の隠ぺいを行うことができるということです。

public interface Player {
 
    // じゃんけんの手
    public static final int GOO = 0;
    public static final int CHOKI = 1;
    public static final int PA = 2;
 
    // じゃんけんの手の数
    public static final int maxSize = 3;
 
    public int getHand();
    public String getName();
    public String toString();
}

インタフェースは単独では生成することはできません。インタフェースをインプリメントしたクラスだけが生成を行うことができ、生成したオブジェクトをインタフェースとして扱うことも可能です。

ここではコンピュータによるプレイヤーを ComputerPlayer クラス、人間によるプレイヤーを HumanPlayer クラスとしましょう。ComputerPlayer クラスは前節の Player クラスとほとんど同じです。違いは出した手をメンバ変数として保持しておくことです。

このようにインタフェースとそれを implements したクラスの関係が実現になります。この実現を図 2-6 のクラス図に記述してみましょう。

Updated Class Chart of Janken
図 2-7 アップデートしたじゃんけんのクラス図

インタフェースも普通のクラスと同様に四角で表しますが、普通のクラスと区別するためにインタフェース名の上に <<interface>> と記述しておきます。このように <<...>> で表されるものをステレオタイプといいます。interface 以外のステレオタイプは後の章で出てきますが、ここではこれがステレオタイプというものがあるということだけ気にとめておいてください。

implements (実現)は点線と白抜き三角で表します。この図では ComputerPlayer と HumanPlayer が Player を implements していることを示しています。Judge は直接 ComputerPlayer や HumanPlayer と関連は持たずに、両方のクラスとも Player として扱っていることを示しています。

インタフェースで定義された操作は implements したクラスでは通常は記述しません。しかし、インタフェースには実装はないので、かならず implements したクラスでは実装を行う必要があります。

図 2-7 では ComupterPlayer と HumanPlayer は同じ定義になっています。インタフェースでは public な関数のみ定義できるので、属性が共通している場合や、private な関数が共通している場合はクラス間の関係のうち「汎化」を使用してまとめることができます。 汎化については次章以降で説明したいと思います。

ここで説明した記述法以外にも簡易的にインタフェースを示すためのロリポップと呼ばれる記述法もあります。

それでは、Player インタフェースを implements したクラスを実装していきましょう。

ComputerPlayer クラスはここで見ることができます ComputerPlayer.java

HumanPlayer は新たに導入したので、詳しく見ていきましょう。

HumanPlayer クラスはここで見ることができます HumanPlayer.java

ComputerPlayer ではコンストラクタでプレイヤーの名前を与えていましたが、HumanPlayer は人間と決まっているのでコンストラクタでは初めから名前を決めてしまいましょう。

    public HumanPlayer(){
        this.name = "You";
    }

ComputerPlayer と HumanPlayer の大きく違うところは、ComputerPlayer が手の選択を乱数を用いて決めているのに対し、HumanPlayer は遊ぶ人に入力してもらう点です。手の選択を行う selectHand は次のように記述してみました。

 1:    private int selectHand(){
 2:        System.out.println("Please input Your Hand.");
 3:        System.out.println("Goo (g), Choki (c), Pa (p)");
 4:        System.out.print("> ");
 5:
 6:        BufferedReader reader 
 7:                 = new BufferedReader(new InputStreamReader(System.in));
 8:        try{
 9:            String inputText = reader.readLine();
10:            if(inputText.equalsIgnoreCase("g")){
11:                hand = GOO;
12:            }else if(inputText.equalsIgnoreCase("c")){
13:                hand = CHOKI;
14:            }else if(inputText.equalsIgnoreCase("p")){
15:                hand = PA;
16:            }
17:        }catch(IOException ex){
18:            System.exit(1);
19:        }
20:
21:        return hand;
22:   }

入力をしてもらうために 2 から 4 行目で画面に出力を行います。その後に入力を受け付けます。入力には Mastermind で使用したのと同じように System.in を使います。実際の入力は java.io.BufferedReader を使用するのも、Mastermind の時と同じです。

6, 7 行目で BufferedReader オブジェクトを生成し、9 行目で 1 行の読み込みを行います。1 行の読み込みには readLine 関数を使用します。

入力された文字列を 10 行目から 16 行目の if 文で場合わけを行い、手をセットします。

これで、ユーザが入力を行う HumanPalyer クラスができました。後は、ComputerPlayer と HumanPlayer に変更したことに Judge クラスを変更していきましょう。

 

 
  Judge クラスの変更  
 

Judge クラスの変更はそれほどありません。というのも、Player インタフェースを導入しても、使い方は前節までの Player クラスと変わりはないからです。変更しなくては行けないのは Player クラスをインプリメントしたクラスを生成する部分です。その部分は Judge クラスのコンストラクタにあります。

 1:    public Judge(int playerSize){
 2:        players = new ArrayList();
 3:        hands = new ArrayList();
 4:
 5:        players.add(new HumanPlayer());
 6:        for(int i = 1 ; i < playerSize ; i++){
 7:            players.add(new ComputerPlayer("Player" + i));
 8:        }
 9:    }

2, 3 行目は今までと同じですが、その後に players に要素を追加する部分が変更する部分です。まず、5 行目で HumanPlayer を生成して、残りは CompterPlayer を生成して players に追加します。players を使用するのは playGame 関数ですが、この部分は変更する必要はありません。 players を使う部分を下記に示しておきます。

 1:    public void playGame(){
 2:         // プレイヤーに手を問い合わせる
 3:        for(int i = 0 ; i < players.size() ; i++){
 4:            Player player = (Player)players.get(i);
 5:            int hand = player.getHand();
 6:            hands.add(new Integer(hand));
 7:        }

4 行目で players の各要素を取り出していますが、HumanPlayer や ComputerPlayer にキャストを行うことはせずに、インタフェースである Player にキャストしています。HumanPlayer と ComputerPlayer は Player として扱うことができるので、この後の部分を変更せずに使用することができるのです。

たかが、じゃんけんといえどもなかなか奥が深いですね。

この節のまとめを次に示しました。

  • 機能の定義と実装を分離させるためにインタフェースを使用する
  • インタフェースを利用する側は実装の違いを気にせずに、まったく同じように機能を使用することができる
  • インタフェースを使用することで、実装を変更しても、クライアント側の変更は最小限にすることができる

ここではクラス間の関係として関連を使用しました。他の関係については次節以降で説明していきたいと思います。

 

 
 

作成したソースファイルとコンパイルを行ったクラスファイルはここでダウンロードできます janken2.zip

(Oct. 2000)

 
 
Go to Previous Page Go to Contents Go to Java Page Go to Next Page