目次はこちら
1-2 じゃんけんゲームの詳細設計〜処理の塊とメンバ変数(フィールド)〜
本章では、1-1で作成したフローチャートを見ながら、実際に起動するクラスを作成していきます。
※クラスの作成方法は、1-1で学習したので記述しません。
そして、フローチャートの四角で囲んだ記述内容をメソッドを作成して、処理を実装する手段を考えていきます。
まとめると処理の塊を作るという見方もできます。
詳細設計ですので「どんな処理を作るか考える」という意味です。コーディングしたほうが早いと思うかもしれませんが、実装前に「テストケースを作成する」工程があるので、まだ考えるだけです。
考えたことをノートやメモにまとめることもよい学習になります。実際にはUMLなどの設計レベルで考えたことをまとめることが多いです。がここは自分なりのメモでよいと思います。
主に行うことは以下の通りです。
本パートで行うこと
- じゃんけんゲームを起動するクラスを作成する
- フローチャートの処理内容をメソッドに置き換える
- 作成した処理を起動できるようにメインメソッドを作成する
- 処理の全体を考える
1.じゃんけんゲームを起動するクラスを作成する
クラスの作成方法に関しては1-1で学習済みですので詳細は記述しません。
本章では、じゃんけんゲームの実装を行うので、じゃんけんゲームの実行クラスを作成します。
クラス名は FirstJankenMain としてください。
そして、パッケージはtutorial以下に作成してください。
<作成例後の完全クラス名> ※パッケージ名 + クラス名
jp.zenryoku.tutorial.FirstJankenMain
※リンク先には筆者が作成した、コードがあります。参考なれば幸いです。
2.フローチャートの処理内容をメソッドに置き換える
次は、クラスの中身を実装していきます。まずは「何を実装するか?」を確認していきましょう。
自分で作成したフローチャートと処理フロー(箇条書き)を見ながら、読み進めてください。
ちなみに、筆者が作成した処理フローは下のようなものです。
Javaの技術をマスターするために
そして、ここからは、例として筆者が作成した処理を実装するサンプルを示していきますので
自分で考えて作成した処理を実装してみてください。
<筆者の作成した処理フロー(箇条書き)>
- 勝敗判定マップを作成しておく
- 「じゃんけん ...」もしくは「あいこで ...」を表示する
- ユーザーの入力待ちをする(標準入力受付)
- 「しょ!」を表示する
- 勝敗判定を行う
- 勝負がついた場合
- ユーザーの勝ち⇒「YOU_WIN」を表示
- CPUの勝ち⇒「YOU_LOSE」を表示
- 勝負がつかない場合(あいこの時)
⇒「あいこで ...」を表示する - 3からもう一度同じ処理を繰り返す
ここで注目してもらいたいのが、処理フロー(箇条書き)でもフローチャートでも処理部分は同じ(意味の)文言になると思います。
つまり、箇条書きにした部分=フローチャートの四角=メソッドになるということです。
<困ったときの対応方法>
実装を行おうとしてどのようにしてわからないときは調べましょう。
例えば、下のコードにある「Mapインターフェースの使い方がわからない」というとき
ブラウザを開き、検索キーワードに「java map 使い方」のように入力して検索してみましょう。
そして検索されたリンクをクリックして、対象のページを読みます。
わからなかったら他のページをみる、書いてあるコードを動かしてみる。などのように試行錯誤しながら理解します。
ここで、「自分で調べて解決する」という技術 を身に着けていきます。これができるようになれば、誰に教わるでもなく物事を解決できるようになっていきます。
くどいようですが、あくまでも自分で考えてプログラムを組むというところに留意してください。
実装サンプルコード
==処理1:実行の準備処理==
1.勝敗判定マップを作成しておく
<サンプルコード1>
このサンプルコードは、筆者が作成したプログラムの一部です。勝敗判定用のMapを作成する処理のサンプルです。
/** ※この部分はJavaDocです。
* 勝敗判定Mapを作成する。
*/
public void createJudgeMap() {
// java.util.Mapインターフェースとjava.util.HashMapクラス
Map<String, Integer> hanteiMap = new HashMap<String, Integer>();
// Mapインターフェースの「productメソッド」を呼び出す。
hanteiMap.put("入力値", 判定用定数);
}
意図としては、入力値にじゃんけんの手(グー・チョキ・パー)を受け取り、出力として勝敗の判定結果を返すマップを作成する、というものです。これを実装上で使うのには下のようにやりました。
<じゃんけんの例>
具体的には、「"グー" = 0; "チョキ" = 1; "パー" = 2;」のように数値(int型)でじゃんけんの手を示すようにして、「プレーヤーの手 + CPUの手」と します。
プレーヤーが「グー(0)」、CPUが「チョキ(1)」だった場合、次のような文字列を作成します。「01」
そして、マップには「キー:01」「値:YOU_WIN」を設定しておきます。
プログラムのコードでは、下のようになります。「"01"」となっている部分は引数ですので、この部分を変数に変えてやれば、色んな手に対応する「勝敗結果」を返却する事ができます。
String result = map.get("01");
System.out.println("勝敗結果=" + result);
この処理では、表示結果として
と表示されます。勝敗結果=YOU_WIN
サンプルコード1にあるメソッドの実装内容は、
- プロジェクト内のどこからでもアクセスできる
- 返却値なしの
- createJudgeMapという名前のメソッドを定義しています。
MapインターフェースとHashMapクラスはインポートする必要があり、以下のように「import文を使用して」インポートします。
このMapインターフェースを使用すれば、キーを渡せば値を取得できるようになります。
自分の実装するときのテクニックの一つとして使ってください。
次のサンプルは、Mapインターフェースを実装するときのサンプルです。次のように使用します。
- map.get(キー)でマップの値を取得する
- map.put(キー, 値)でマップにキーと値を設定する
<Mapインターフェースのサンプル>
Mapインターフェースを実装するときの書き方を示したものです。
import java.util.HashMap;
import java.util.Map;
public class クラス名 {
// java.util.Mapインターフェースとjava.util.HashMapクラス
Map<String, Integer> hanteiMap = new HashMap<String, Integer>();
// Mapインターフェースの「getメソッド」「putメソッド」を呼び出す。
hanteiMap.get("キー");
hanteiMap.put("キー値", 勝敗判定);
}
コードの解説
データ型 = Mapクラス、MapクラスのキーにString型と、値にはInteger型を定義しています。
そして、java.util.Mapインターフェース型の変数にHashMapクラスを代入しています。
変数の初期化の式は、以下のように書くルールになっていますので、データ型と変数名に「=」で変数へと値を代入します。
プログラミングテクニック
<変数の初期化方法>
データ型 変数名 = 値(参照型=クラスの場合は、new クラス名();)
クラスを変数に代入するときに「new」と書きますが、これは「インスタンス化」と呼びます。
そして、MapインターフェースにHashMapを代入する式が以下のものです。
<Mapインターフェースの初期化>
Map<String, Integer> hanteiMap = new HashMap<String, Integer>();
補足として、下のジェネリクスに関して記述します。「<」と「>」で囲っている部分のことです。
<ジェネリクスの中身について>
<String, Integer>
これはキーと値のデータ型を設定しています。これでキーに渡す値のデータ型(Stringなのかintなのか)がはっきりします。
Mapインターフェースの使い方
HashMap<K, V>
JavaDoc(MapとHashMap)では左のような記述があります。これの「K」はキーを示します、
同様に「V」は値を示します。
HashMapクラスは、Mapインターフェースをimplementsしているので、下のように初期化することができます。
<HashMapクラスの初期化>
Map<String, Integer> hanteiMap = new HashMap<String, Integer>();
インターフェースの扱いに関しては、2-1「4.インターフェースクラスについて」に記述します。
インターフェースを実装(implements)したクラスは、インターフェース型の変数として使用 することができます。
現状では、HashMapクラスは、「Mapインターフェースを実装(implements) しているのだな」と理解しておいてください。
つまるところ、HashMap<String, Integer> というコードはキーが「String
」、値は「Integer(int)
」ですよ。という意味です。
具体的には下の様に使用します。
ちなみに、Integerクラスはint型としても、Integerクラスとしても使用できます。
<サンプルコード2>
MapのキーにString, 値にIntegerを使用し、キーと値を設定し、取得する場合のサンプルです。
Map<String, Integer> hanteiMap = new HashMap<String, Integer>();
// Integerクラスはint型のラッパークラスです。
hanteiMap.put("aaa", 1);
Integer value = hanteiMap.get("aaa");
int value2 = hanteiMap.get("aaa");
System.out.println("Mapの値: " + value.toString());
- Mapインターフェース型の変数「hanteimap」へHashMapのインスタンスを代入
- 生成したHashMap(Mapインターフェース)のputメソッドで、Mapへデータを登録
- キー"aaa"を使用して値を取得
練習問題にもMapインターフェースの扱いに関して記述していますので、そちらも参照ください。
練習問題
練習問題のやり方
「1-4 じゃんけんゲームの設計をする〜練習問題〜」に詳細を記述しています。以下のコードはMapインターフェースを使用したときの実装例とサンプル問題です。
==練習問題とサンプル・コード:Mapを使用する(Mondai4)==
Mondai4のリンクは、Mapインターフェースを使用したプログラムのサンプルです。「Mapインターフェースの使い方を学習したので実際に自分でコードを書いてみるという練習をしてください。
問題
MapインターフェースにHashMapを代入してMapに以下のような情報を登録してください。
キー(数値) | 値(文字列) |
---|---|
0 | red |
1 | blue |
2 | yellow |
そして、標準入力の値によって表示する内容を分けて表示してください。
- 「0」の場合は"red"をコンソールに表示
- 「1」の場合は"blue"をコンソールに表示
- 「2」の場合は"yellow"をコンソールに表示
表示結果は下のようなイメージです。キーに対して対象になる値が取得できていればOKです。
<パターン1>
色コードを入力してください。
0
red
<パターン2>
色コードを入力してください。
1
blue
<パターン3>
色コードを入力してください。
2
yellow
==処理2:初期表示==
「じゃんけん ...」もしくは「あいこで ...」を表示する
これも、単純な処理になると思います。ただし、「IF文」が出てきます。
IF文の実装方法などは、1-1にて記述済みですので割愛します。
自分が作成した方法では、この処理はTRUE, FALSEを判定するための材料を引数に依存しています。
具体的に以下のような実装を行いました。
<サンプルコード3>
メソッドを使用して、条件別の処理を実装するときのサンプルです。
引数に「じゃんけん」を表示するのか、「あいこで」を表示するのか判定する値(boolean)を持っています。なので呼び出すときは
とすれば「じゃんけん」が表示されます。printJankenAiko(true)
逆に
とすれば、「あいこで」が表示されます。printJankenAiko(false)
public void printJankenAiko(boolean isJanken) {
// isJankenがtrueの時は「じゃんけん」を表示する
if (isJanken) {
System.out.println("じゃんけん ...");
} else {
System.out.println("あいこで ...");
}
}
- メソッドの引数がtrueの場合は「じゃんけん ...」を表示
- メソッドの引数がfalseの場合は「あいこで ...」を表示
この様なメソッドを用意しておけば、引数に渡す値によって判定結果別の処理を行うことができます。
==Lv.2練習問題2:例外処理を書く(Mondai5)==
ここで、また練習問題を行います。プログラムを書いているときに想定通りの値が入力されない、もしくは、想定外の引数(パラメータ)を作成したメソッドに受け取ってしまうことがあります。
それらの問題を解消するために例外処理があります。
具体的には、以下のコードのように、数字を引数に渡さないといけないけれど通常の文字を渡した場合「NumberFormatException」の例外が発生します。これを回避するのに以下の方法があります。
- 入力チェック処理を入れて数字(0-9)のみを許可する
public void sampleCheck(String input) throws NumberFormatException { if (input.matches("[0-9]") == false) { // 1文字のみのチェックになる new NumberFormatException("数字を入力してください"); } // 処理の続き }
- NumberFormatExceptionが発生したら、エラーを返す
public boolean sampleTryCatch(String input) { try { Integer.parseInt(input); } catch(NumberFormatException e) { System.out.println("数字を入力してください"); return false; } return true; }
問題
プログラム引数が「Hello」のときに「Hello World」を表示してください。
- 「Test」の時に「Testing now!」を表示
- それ以外のプログラム引数が入ってきたときは「Please input "Hello" or "Test"」と表示してアプリケーションを終了する
※ヒント
アプリケーションの強制終了方法
System.exit(0); // 正常終了の場合
System.exit(-1); // 異常終了の場合
==処理3:ユーザーの入力待ち==
「じゃんけん ...」の後はユーザーが「グー、チョキ、バー」のどれかを入力する必要があります。
なので、ユーザーの入力を受け付ける処理を実装する必要があります。
ここでもJavaAPIを使用します。「標準入力」というものを使用します。
というか入力の受け付けは標準入力に始まります。
例えば、ゲームなどで「Aボタン」を入力したときには押下したボタンに対応するデータ(アナログパッドの場合はなんだかよくわからない文字=バイト配列)が標準入力に渡されます。
そして、今回の実装では、標準入力を受けるために以下のクラスを使用します。
このクラスは、読み取るときに参照するデバイス(クラス)を引数に渡してあげる必要があります。
今回は「標準入力」なので、標準入力を表現しているSystem.inを渡します。※JavaAPIです
コンソールに文字列などを表示するときには「標準出力」を使用しました。互いにINとOUTの関係です。
<サンプルコード4>
標準入力を使用して、コンソール入力から文字列を受け取るプログラムのサンプルです。scan.nextLineで入力値一行分(Enterが押されるまでの文字列)を受け取ることができます。
public String acceptInput() {
Scanner scan = new Scanner(System.in);
// 標準入力から読み取り
String input = scan.nextLine();
return input;
}
- 標準入力を引数に渡して、Scannerのインスタンスを生成
- 標準入力の受け付けを行い、入力した値を取得する(データ型は文字列(String))
- 取得した値を返却
==Lv.2練習問題3:例外処理を書く2(Mondai6)==
ここで、また練習問題を行います。標準入力を使用する練習です。
問題
「Lv.2練習問題2」では、プログラム引数を使用しましたが、プログラム引数の代わりに
標準入力を使用して「Lv.2練習問題2」と同じ処理を行うプログラムを作成してください。
==処理4: 繰り返し(ループ)処理の書き方==
ループ処理には以下の2種類があります。
FOR文とWHILE文です。
<FOR文>
for (カウンター変数の初期化; ループする条件; 後処理) {
// 繰り返し処理の中身
}
<WHILE文>
while (ループする条件) {
// 繰り返し処理の中身
}
<FOR文のサンプルコード>
コンソールに1~10の数字を表示します。
// 表示する、カウントアップする数値
int num = 1;
for (int i = 0; i < 10; i++) {
// 表示する数字をインクリメント
num++;
System.out.println("カウント:" + num);
}
<WHILE文のサンプルコード>
// 表示する、カウントアップする数値
int num = 0;
while ( num < 11) {
// 表示する数字をインクリメント
num++;
System.out.println("カウント:" + num);
}
どちらを使用しても、良いですが状況によって簡単な方(見やすい方)を用するのがよいと思います。
上記のように、ループ文には、大まかにこの2種類あります。
さらに細かくわけると、「do-while文」などがありますが、あまり使わないので割愛します。
==Lv.2練習問題4:ゲームループを作る(Mondai7)==
ここで、また練習問題を行います。標準入力を使用し、さらに無限ループを使用する練習です。
問題
標準入力をうけて、入力された文字列を表示するプログラムを作ってください。
ただし、"bye"と入力したときにコンソールに"Good bye!"と表示するまで、何度も処理を繰り返すようにしてください。
<表示する例>
文字列を入力してください。
aaa
あなたは「aaa」と入力しました。
文字列を入力してください。
bye
Good bye!
※アプリ終了
==処理5:勝敗判定別の処理==
勝敗判定の結果は3種類あります。
- ユーザーの勝ち
- ユーザーの負け
- あいこ
上記の3ケースの処理をハンドル(操作)する必要があります。
今までの学習内容で行くと「IF文」一択ですが、ここで選択肢を増やします。
==switch文の書き方==
上のサンプルコードで示したように、switch(判定する値) { ... } の判定値「result」が、勝ち=0
, 負け=1
, あいこ=2
の各ケースに対応する処理を行います。
ちなみに、IF文でも同じ処理を書くことができます。
<switch分と同じ意味のIF文の例>
if (result == YOU_WIN) {
// 勝ちの時
} else if (result == YOU_LOSE) {
// 負けの時
} else if (rsult == AIKO) {
// あいこの時
} else {
// 想定外の値の時
// ※例外処理として実装することをお勧めします。
}
if, else if, elseで条件を分岐しています。これをswitch文で書くと下のようになります。
switch (result) {
case YOU_WIN:
// 勝ちの時
System.out.println("YOU_WIN");
break;
case YOU_LOSE:
// 負けの時
System.out.println("YOU_LOSE");
break;
case AIKO:
// あいこの時
System.out.println("AIKO");
break;
default:
// 想定外の値の時
// ※例外処理として実装することをお勧めします。
System.out.println("ERROR!");
}
switch文の場合は、各ケースの処理を行った後に「break文」が必要です。
でないと、下のように、コンソール出力されることになります。
<result=0の場合>
YOU_WIN
YOU_LOSE
AIKO
ERROR!
<result=1の場合>
YOU_LOSE
AIKO
ERROR!
<result=2の場合>
AIKO
ERROR!
これは想定した結果にはならないので「break」が必要になるというわけです。
==例外処理==
switch文の場合は「default」で想定外の処理をおこなっていましたが、
Javaでは、メソッドに例外を処理する書き方があります。
- throws文の書き方
メソッド throws 例外クラス { ... }
<throws文のサンプルコード>
public void createReigai() throws Exception {
// 何かしらの処理
}
- try~catchの書き方
try { ... } catch(例外クラス 変数名) { ... }のような形で書きます。
<try~catchのサンプルコード>
public void testAAA() {
try {
// 例外を出す可能性のあるメソッド呼び出し
createReigai();
} catch(Exception e) { // ExceptionをNullPointerException等にしてもよい
// 例外の内容を標準出力に出力する
e.printStackTrace();
}
}
この例では「createReigai()」は「throws」文を使用して例外を投げることを定義しています。
これに対して上の「testAAA()」ではtry~cahtchで囲んでいます。
これらの違いは、例外をメソッドの中で処理してしまうか、メソッドの呼び出しもとで処理するかの違いです。
ここまで学習した文法を使用すれば、フローチャートの四角(処理フロー(箇条書き))部分は
実装できると思います。各処理をどのように実装するかプログラムレベルで設計してみてください。
<プログラムレベルの設計サンプル>
※これは文章で詳細設計を行った場合です。筆者がわかるように書きました。
処理フロー(箇条書き)⇒実装するメソッド名 ※処理が書いてあるときはメソッドを作らない
・処理の内容1
・処理の内容2
・
・
・
-
勝敗判定マップを作成しておく⇒createJudgeMap()
- Mapを作成する
- Mapに勝敗パターンをすべて登録する
- クラス内で参照できるようにフィールド変数に格納する
-
「じゃんけん ...」もしくは「あいこで ...」を表示する⇒printJankenAiko()
- 引数にisJankenを持たせる
- isJankenがTRUEのときは「じゃんけん ...」を表示
- isJankenがFALSEのときは「あいこで ...」を表示
-
ユーザーの入力待ちをする(標準入力受付)⇒acceptInput()
- 標準入力を受け取り、入力値を返却する
-
「しょ!」を表示する⇒printPonOrSho()
※フローチャートを書いていた時に考慮が漏れていた- 引数にisJankenを持たせる
- isJankenがTRUEのときは「ポン!」を表示 ※考慮漏れの部分
- isJankenがFALSEのときは「しょ!」を表示
-
勝敗判定を行う⇒judgeWinLose()
- ユーザーの手とCPUの手を連結する
- 勝敗判定マップから勝敗結果を取得して返却
-
勝負がついた場合⇒5で受け取った勝敗判定結果で以下の処理を行う
- ユーザーの勝ち⇒「YOU_WIN」を表示
- CPUの勝ち⇒「YOU_LOSE」を表示
-
勝負がつかない場合(あいこの時)
⇒「あいこで ...」を表示する -
「あいこ」の場合、3からもう一度同じ処理を繰り返す、そうでなければ処理を終了する。
3.作成した処理を起動できるようにメインメソッドを作成する
今までに、作成したメソッドは以下のものになります。
- createJudgeMap()
- printJankenAiko()// 引数は省略しています。
- acceptInput()
これらをメインメソッドで順番に呼び出してやれば処理フロー(箇条書き)の1~3が作成できます。
- 勝敗判定マップを作成しておく
- 「じゃんけん ...」もしくは「あいこで ...」を表示する
- ユーザーの入力待ちをする(標準入力受付)
<サンプルコード5>
作成した処理のサンプルになります、あくまでもサンプルであり、読者の皆さんが実装する時の参考程度のものです。処理の内容に関しては後述してあります。
public static void main(String[] args) {
createJudgeMap()
// 「じゃんけん」を表示する場合
printJankenAiko(true);
String input = acceptInput();
// 途中省略
// 勝敗判定結果を取得
int result = judgeWinLose("ユーザーの手", "CPUの手")
// 勝敗判定結果で以下の処理を行う
switch(result) {
case YOU_WIN:
// 処理1
break;
case YOU_LOSE:
// 処理2
break;
case AIKO:
// 処理3
break;
}
}
上のコードは、以下の2種類の説明ができます。
<説明パターン1>
- createJudgeMap()を呼び出す
- printJankenAiko()を呼び出す
- acceptInput()を呼び出し、String型のデータを受け取る
~以下省略~
<説明パターン2>
- createJudgeMap()で勝敗判定マップを作成
- printJankenAiko()で「じゃんけん」を表示
- acceptInput()で、ユーザーの入力した値を受け取る
~以下省略~
-
説明パターン1にはそれぞれのメソッドの内容が書いていない
-
説明パターン2にはそれぞれのメソッドの内容が書いてある
この様に、メソッドを作成してそれを呼び出す形で実装すれば、処理を説明するのが簡単になります。
逆に言うと、処理を考えるのが楽になります。
つまり、「処理の塊=メソッドの中身」のみを考えればよいということです。
余談ですが、これをメソッドをかまさないで実装した場合は下のようなコードになります。
==処理を分割することのメリット==
メソッドを作成し、「勝敗判定を行う」などの処理をメソッド内にまとめてしまうとメインメソッドの中では、「勝敗判定を行う」メソッドを呼ぶだけでよくなります。
例えば、じゃんけんゲーム(簡易版)をメソッド分けしないで、作成すると下のようなコードになります。
実装する量が少ないので、メリットがわかりにくいですが見てほしいです。
<メソッド分けしないサンプルコード1>
public static void main(String[] args) {
Map<String, Integer> hanteiMap = new HashMap<String, Integer>();
// Integerクラスはint型のラッパークラスです。
hanteiMap.put("aaa", 1);
boolean isJanken = true;
// 「じゃんけん」または「あいこ」
if (isJanken) {
System.out.println("じゃんけん ...");
} else {
System.out.println("あいこで ...");
}
// ユーザーの入力を受ける
Scanner scan = new Scanner(System.in);
String input = scan.nextLine();
}
このコードでは、メソッド分けするメリットがわかりずらいですが、これをメソッド分けしないで動くレベルで実装してみます。※これはGithubにアップしてあります。
public class Lv2NoMethodImplements {
public static void main(String[] args) {
/* 勝敗判定フラグ:勝ち(ユーザー) */
final int YOU_WIN = 0;
/* 勝敗判定フラグ:負け(ユーザー) */
final int YOU_LOSE = 1;
/* 勝敗判定フラグ:あいこ(ユーザー) */
final int AIKO = 2;
/* グー */
final String GU = "0";
/* チョキ */
final String CHOKI = "1";
/* パー */
final String PA = "2";
// 勝敗判定マップを作成
Map<String, Integer> hanteiMap = new HashMap<String, Integer>();
// プレーヤーの勝ちケース
hanteiMap.put(GU + CHOKI, YOU_WIN);
hanteiMap.put(CHOKI + PA, YOU_WIN);
hanteiMap.put(PA + GU, YOU_WIN);
// プレーヤーの負けケース
hanteiMap.put(GU + PA, YOU_LOSE);
hanteiMap.put(CHOKI + GU, YOU_LOSE);
hanteiMap.put(PA + CHOKI, YOU_LOSE);
// あいこのケース
hanteiMap.put(GU + GU, AIKO);
hanteiMap.put(CHOKI + CHOKI, AIKO);
hanteiMap.put(PA + PA, AIKO);
// 手のマップ
Map<String, String> teMap = new HashMap<String, String>();
teMap.put(GU, "グー");
teMap.put(CHOKI, "チョキ");
teMap.put(PA, "パー");
// CPUの手を生成するJavaAPI
Random random = new Random();
// じゃんけん愛顧のフラグ
boolean isJanken = true;
// 無限ループ
while(true) {
// 「じゃんけん」または「あいこ」
if (isJanken) {
System.out.println("じゃんけん ...");
} else {
System.out.println("あいこで ...");
}
// ユーザーの入力を受ける
Scanner scan = new Scanner(System.in);
String input = scan.nextLine();
// CPUの手を取得する(JavaSEのAPIを使用するのでテストしない)
String cpuTe = String.valueOf(random.nextInt(2));
// 4.「ポン!」or「しょ!」を表示
if (isJanken) {
System.out.println("ポン!");
} else {
System.out.println("しょ!");
}
// 勝敗判定用のキーを取得
String key = input + cpuTe;
int result = hanteiMap.get(key);
//
boolean isFinish = true;
// 勝敗判定結果を表示する
switch(result) {
case YOU_WIN:
System.out.println("YOU WIN!");
isFinish = true;
isJanken = true;
break;
case YOU_LOSE:
System.out.println("YOU LOSE!");
isFinish = true;
isJanken = true;
break;
case AIKO:
isFinish = false;
isJanken = false;
System.out.println("DRAW!");
break;
}
if (isFinish) {
break;
}
}
}
}
メソッド分けしないサンプルコード2の処理内容
- 定数を初期化
- 勝敗判定マップの作成
- プレーヤーとCPUの出した手の判定用マップ作成
- CPUの手を生成するための乱数生成部品のインスタンスを生成
- 無限ループ処理を開始
- isJankenがtrueの時は「じゃんけん ...」falseの時は「あいこで ...」を表示
- ユーザーの入力を受ける
- CPUの手を取得
- isJankenがtrueの時は「ポン!」falseの時は「しょ!」を表示
- 勝敗判定用のキーを取得
- 勝敗判定マップで勝敗判定結果(勝ち=0, 負け=1, あいこ=2)を取得する
- 勝ち、負け、引き分けを表示して、引き分けの時はもう一度処理を行う。
※switch文でresultの値が「勝ち=0, 負け=1, あいこ=2」の各ケース別の処理を行っています。
そして、メソッド分けしないサンプルコード2の実装は、メソッドで処理をまとめていないので
メインメソッドの中に全部突っ込んだ感じです。このようにすると、どこを修正するか調べるときに
ソースコードを全部見ないといけません。
なぜなら、boolean(isFinish)で繰り返しをするかしないか判定しているからです。
処理としては、下の部分です。この処理で、無限ループ処理を抜けます。
<無限ループ処理を抜ける実装サンプル>
if (isFinish) {
break;
}
変数isFinishが、true か falseか判定するための処理が上の方にあります。
この様な状態のことを依存関係が強いといいます。
「依存関係が強い」と、どこかを修正したら、他の部分の修正も必要になります。
大きなプログラムで、こんなことをやってしまった日には、
毎日終電で帰れるかどうかの瀬戸際に立たされます。
どんなに恐ろしいことか、言葉では表現できないのでちょいと小話をします。
==プログラマー怪談その1==
実際に仕事で、プログラムを運用していたとします。
物流系のシステムで、在庫管理システムを運用していたとします。
バグがあり、修正箇所を見つけたのはよいのですが修正する箇所で使用している変数が
グローバル変数(全クラスから参照できる変数)だった。。。会社に3,4日宿泊することになったそうです。
これは、先輩のプログラマーに聞いた話ですが、
対応した人は、体力が尽きて入院したとかしないとか。。。
以上のような、恐ろしいことになります。
筆者も、品川から、埼玉までタクシーで帰ったことがありますが、もうそんなことはやりたくありません。
というわけで、「依存関係」は緩く、出来れば分断したいですね。
※ちなみに、この依存関係の分断はDI(Dependency Injection)という技術で実現されています。有名なフレームワークとしてSpring frameworkというのがあります。
4.処理の全体を考える
上の怪談のようなケースもありますが、上のサンプル、メソッド分けしないパターンで実装すると変数の参照に困りません。
なぜかというと、全部同じスコープ内にあるからです。⇒メインメソッドの中。
メソッド分けすると、スコープの外にあるローカル変数は参照することができません。
この問題を解決するには、フィールド変数を使用します。
ここで「ローカル変数」と「フィールド変数」という言葉がでてきましたが、具体的には下のようなものです。
ついでなので、定数に関しても記述します。
- 定数:決まった値で、変数の中にあっても値を変更することがない(final修飾子がつく)
- フィールド変数:クラス内ならどこからでも参照することができる変数
※修飾子が「public」ならば、クラスの外からでも参照することができる。サンプルコードにある定数はローカル変数 - ローカル変数:メソッドの中で宣言、初期化される変数。メソッドのスコープ外からは参照できない。
<定数を使用したサンプルコード>
public class Lv2NoMethodImplements {
/** 下のものはフィールド変数、かつ定数 */
/** 勝敗判定フラグ:勝ち(ユーザー) */
private final int YOU_WIN = 0;
/** 勝敗判定フラグ:負け(ユーザー) */
private final int YOU_LOSE = 1;
/** 勝敗判定フラグ:あいこ(ユーザー) */
private final int AIKO = 2;
/** フィールド変数:宣言のみでも、初期化してもよい */
/** 標準入力受付部品 */
private Scanner scan;
/** 勝敗判定Map(初期化した場合) */
private Map<String, Integer> hantei = new HashMap<String, Integer>();
/** メインメソッド */
public static void main(String[] args) {
// 勝敗判定マップを作成
Map<String, Integer> hanteiMap = new HashMap<String, Integer>();
// プレーヤーの勝ちケース
hanteiMap.put(GU + CHOKI, YOU_WIN);
hanteiMap.put(CHOKI + PA, YOU_WIN);
hanteiMap.put(PA + GU, YOU_WIN);
// 手のマップ
Map<String, String> teMap = new HashMap<String, String>();
teMap.put(GU, "グー");
teMap.put(CHOKI, "チョキ");
teMap.put(PA, "パー");
// CPUの手を生成するJavaAPI
Random random = new Random();
// じゃんけん愛顧のフラグ
boolean isJanken = true;
/** ~中略~ */
}
}
上のコードでは、1のものが、定数(フィールド変数)、2のものがフィールド変数、3のものがローカル変数です。
<変数名とその用途>
1:定数
- YOU_WIN
- YOU_LOSE
- AIKO
2:フィールド変数
- scan
- hantei
3:ローカル変数
- hanteiMap
- teMap
- random
- isJanken
上のコードで使用している変数名をそのまま抜き出したので、上記のコードと一緒に見てください。
そして、ローカル変数は、メソッドの中でのみ参照することができる。つまり、メソッドの外から参照できない、ガベージコレクションによりメモリが解放されるという意味になります。
メインメソッドで処理(メソッドを呼び出す)部分を考える
初めに作成した処理フローとフローチャートを見ながらメインメソッドで
どのような順番でメソッドを呼び出してやればよいか、条件分岐(IF文)や繰り返しを使用して実装してみてください。
本パートでは以上になります。
大まかに作成したプログラムの処理(フローチャート)をどのように実装したらよいか?イメージがつかめたと思います。Javaの基本も学習しました。
ポイントとしては、フローチャートで行う各処理をメソッドで分割してやるとメインメソッドで処理を実行するときには順番に呼び出すだけになるので楽に考えることができるというところです。
これが、大きなシステムになるとややこしくなってきますので、なるべくシンプルに作成するところがミソです。
次のパートでは今まで作成した処理の全体を見直してみます。
いわゆる「確認作業」です。
この確認の最中に、設計レベルで足りない処理などを発見することもあります。
そして、テストと実装の準備=JUnitの使い方に関しても学習します。