Java Mid Basic〜リファクタリングLv2 処理の整理とクラス分けを行う〜

イントロダクション

前回 リファクタリングの構成について考えました。今回は、実装に入りたいと思います。

今回は、42行あったメインメソッドのコードを整理(リファクタリング)して17行にします。コードをすっきりさせました。

元々あったコード(Lv2_8_Map)を修正してこちら(Lv3_1_RefactorLv2)のように修正しました。

クラス分け

まずは、このアプリ全体の流れを作ります。「流れ」というのは、入力〜出力までの処理順の事です。

この流れをワンパターンにしてシンプルな形にしようという訳です。

今回のコードは、プロパティファイルの中に定義している。キーをコマンドに見立てて、プログラムで次のような処理を行います。

  1. 標準入力をつけとる
  2. 受け取った文字列をコマンドに見立て、紐づく処理を実行
  3. 各処理を行うクラスは「CommandIF」というインターフェースを実装(implements)している

どーやるか?

インターフェースを使います。以前やったプロパティファイルと連携してコマンドを好きなだけ作れる…しかしプログラムには手を入れない…というオブジェクト指向の基本ワザを使います。

基本ワザ?

基本ワザとは「インターフェースクラスを使って、同じメソッド名だけど別な処理を実行する」ということです。ポリモーフィズムとか呼ばれているものです。

Listインターフェースの場合

<プロパティファイル>
コマンド1=クラスの完全名
title=jp.zenryoku.sample.lv3.cmd.Title

<Javaファイル>
上記のプロパティファイルからクラスを呼び出し、そのクラス(CommandIFを実装)を呼び出す仕組みを作る

というような実装を行います。

概要

コマンドからクラスの完全名を取得する部分に関しては、以前やったものをちょいといじって使用できます。つまり考え方は同じということです、下にリンクを記載しておきます。よかったらどうぞ。

プロパティファイルを読み込む

Discordでコマンドを好きなだけ作る方法

<手順>

  1. コマンドの入力を受けてコマンド実行クラスを取得する
  2. 実行クラスのメソッド「execute()」を実行
  3. 処理結果をコンソールに出力

どのコマンドが来ても起動できるように実装します。
当然、例外処理(想定外のコマンド)も実装します。

CommandIF(インタフェース)を使用したポリモーフィズムの実行動画があります。

リファクタリング開始

元のコードは、こちらのコードです。前回作成したMap読み込みのコードです。記載した記事はこちらです。
記事の方に実装方法など細かい部分を記載しています。

今回はリファクタリングを行います。

ダメな部分

これに関しては、こちらの記事に記載しましたが、早い話がごちゃごちゃして見づらいコードだということです。
これらを解消するのに、どーしたら良いか?を考えたのが上の記事になります。UMLなど便利だと思います。

処理フローを作る

早い話が、このアプリケーションで行う処理をワンパターン化しようということです。そのパターンというのが、上に記載したものです。

<手順>
1. コマンドの入力を受けてコマンド実行クラスを取得する
2. 実行クラスのメソッド「execute()」を実行
3. 処理結果をコンソールに出力

これを実装するのに、プロパティファイルとインターフェースを使用します。リファクタリングをしながら実装します。

リファクタリング開始

ますは、メインメソッドを整理します。
現状は以下のようになっています。
MainMethod V1

public static void main(String[] args) {
    // 今回は作成したこのクラスを使用します。
    Lv2_8_Map myClass = new Lv2_8_Map();

    Scanner input = new Scanner(System.in);

    while(true) {
        System.out.print("入力してください: ");
        String inStr = input.nextLine();
        // プロパティファイルの値を取得する
        String value = myClass.getPropertes(inStr);
        if (value == null) {
            System.out.println("プロパティのキーがありません");
        } else {
            System.out.println("プロパティのキー: " + inStr);
            System.out.println("プロパティの値: " + value);
            continue;
        }
        if ("hello".equals(inStr)) {
            System.out.println("Hello World!");
        } else if (inStr.matches("[0-9].*[0-9]")) {
            String answer = myClass.calculate(inStr);
            System.out.println("答えは:" + answer + "です。");
        } else if ("dir".equals(inStr)) { 
            // Dirコマンドを実装する
            myClass.dirCommand();
        } else if ("title".equals(inStr)) {
            myClass.showTitle();
        } else if (inStr.startsWith("move")) {
            myClass.showPositionInfo(inStr);
        } else if ("bye".equals(inStr)) { 
            myClass.commandHistory();
            break;
        } else {
            System.out.println("想定外の入力です");
            break;
        }
        myClass.addCommand(inStr);
        System.out.println("Next command ... ");
    }
    System.out.print("AP is fiinished. Bye!");
}

まぁ長い。。。Java Mid Basicを記載する度に追加して来たコードですので、整理されていません。
そんなわけで、まずはSystem.out.println()のコードを1つだけにしようと思います。
下のメソッドを追加します。

/**
 * コンソール出力するメソッド。
 * @param message コンソール出力する文字列
 */
public static void printMessage(String message) {
    System.out.println(message);
}

そしてSystem.out...の部分を全て上のメソッドに置き換えます。メインメソッドで使用するので修飾子「static」をつけます。staticのついたメソッドはアプリケーション内で1つだけ作成することができます。逆にいうと名前が同じでなければいくらでも作れます。
そして、上記のSystem.out.println()System.out.print()の両方を使用しているのでこの2つを使い分けるために引数を追加します。
実装としては以下のメソッドを追記します。

/**
 * コンソール出力するメソッド。
 * @param message コンソール出力する文字列
 * @param withLn 改行するかしないか
 */
public static void printMessage(String message, boolean withLn) {
    if (withLn) {
        System.out.println(message);
    } else {
        System.out.print(message);
    }
}

このように、メソッドの名前が同じで、引数の数が違うメソッドを作成するとき「オーバーロード」と呼びます。

修正したコードは下のようになりました。(メインメソッド)
MainMethod V2

public static void main(String[] args) {
    // 今回は作成したこのクラスを使用します。
    Lv2_8_Map myClass = new Lv2_8_Map();

    Scanner input = new Scanner(System.in);

    while(true) {
        printMessage("入力してください: ", false);
        String inStr = input.nextLine();
        // プロパティファイルの値を取得する
        String value = myClass.getPropertes(inStr);
        if (value == null) {
            printMessage("プロパティのキーがありません");
        } else {
            printMessage("プロパティのキー: " + inStr);
            printMessage("プロパティの値: " + value);
            continue;
        }
        if ("hello".equals(inStr)) {
            printMessage("Hello World!");
        } else if (inStr.matches("[0-9].*[0-9]")) {
            String answer = myClass.calculate(inStr);
            printMessage("答えは:" + answer + "です。");
        } else if ("dir".equals(inStr)) { 
            // Dirコマンドを実装する
            myClass.dirCommand();
        } else if ("title".equals(inStr)) {
            myClass.showTitle();
        } else if (inStr.startsWith("move")) {
            myClass.showPositionInfo(inStr);
        } else if ("bye".equals(inStr)) { 
            myClass.commandHistory();
            break;
        } else {
            printMessage("想定外の入力です");
            break;
        }
        myClass.addCommand(inStr);
        printMessage("Next command ... ");
    }
    printMessage("AP is fiinished. Bye!");
}

次は、プロパティファイルの部分です。プロパティファイルから値を取得、キーが存在しなければ、ワーニングを出力する。という処理です。これをメソッドに切り出します。
ちょいと注意です。メソッドに切り出した時に「myClass」が元の実装だとスコープ外に来てしまうためビルドエラーが出ます。これはmyClassをフィールド変数に引越しすることで回避できます。
private static Lv3_1_RefactorLv2 myClass;
そして、初めに作成した"hello"と入力された時の処理はif文で繋げていたのでこの部分をごっそりメソッドに切り出します。

/**
 * 初めに実装したもの
 * @param cmd 入力コマンド
 */
public boolean basicMethod(String cmd) {
    boolean isEnd = false;
    if ("hello".equals(cmd)) {
        printMessage("Hello World!");
    } else if (cmd.matches("[0-9].*[0-9]")) {
        String answer = myClass.calculate(cmd);
        printMessage("答えは:" + answer + "です。");
    } else if ("dir".equals(cmd)) { 
        // Dirコマンドを実装する
        myClass.dirCommand();
    } else if ("title".equals(cmd)) {
        myClass.showTitle();
    } else if (cmd.startsWith("move")) {
        myClass.showPositionInfo(cmd);
    } else if ("bye".equals(cmd)) { 
        myClass.commandHistory();
        isEnd = true;
    } else {
        printMessage("想定外の入力です");
        isEnd = true;
    }
    return isEnd;
}

これで、メインメソッドの中がスッキリしました。

public static void main(String[] args) {
    // 今回は作成したこのクラスを使用します。
    myClass = new Lv3_1_RefactorLv2();

    Scanner input = new Scanner(System.in);

    while(true) {
        printMessage("入力してください: ", false);
        String inStr = input.nextLine();
        String propStr = myClass.getPropString(inStr);
        if (myClass.basicMethod(inStr)) {
            break;
        }
        myClass.addCommand(inStr);
        printMessage("Next command ... ");
    }
    printMessage("AP is fiinished. Bye!");
}

あとは、下の方に溜まっているメソッドたちをなんとかしたいところですが、先にインターフェースの実装に着手しようと思っています。次回にインターフェースの実装をやります。
最終的に出来上がったコードはGitにアップしてありますので、よかったらどうぞ。
Lv3_1_RefactorLv2_Main
でわでわ。。。

<<< 前回 次回 >>>

投稿者:

takunoji

音響、イベント会場設営業界からIT業界へ転身。現在はJava屋としてサラリーマンをやっている。自称ガテン系プログラマー(笑) Javaプログラミングを布教したい、ラスパイとJavaの相性が良いことに気が付く。 Spring framework, Struts, Seaser, Hibernate, Playframework, JavaEE6, JavaEE7などの現場経験あり。 SQL, VBA, PL/SQL, コマンドプロント, Shellなどもやります。

コメントを残す