Java Mid Basic 〜チェッカークラスを作る〜

オブジェクト指向の考え方では1クラス1機能です。前回作成した、数秘術→社会人基礎力の数値を算出するクラス内に入力チェック処理を作るとあの処理、この処理…と面倒なので、チェッカークラスを作成します。

そして、staticの使い方に関しても触れます。

チェッカークラスは、チェック処理を担当するクラスです。

コマンドクラスから呼び出す

コマンドクラスはインターフェースを使用してCommandIFを実装したクラスであればなんでも良い様に作成しました。

今度は、入力チェック用のクラスを作成します。ここで大切なのは「どの様に使うか?」です。

今回は、シンプルにチェッカーユーティリティクラスを作成します。クラス名は「CheckwrUtil」にします。

使い方は以下の様に静的呼び出しで行います。理由は、インスタンス化する意味がないからこの様な形で実装します。

CheckerUtil.isMandatory("文字列");

クラスの実装は下の様になります。

public class ChckerUtil {
    public static boolean  isMandatory(String inut) {
        // チェック処理
    }
}

そして、必須入力チェックなので、入力の有無を判定し、TrueかFalseを返却するように実装してやればオッケー。

具体的に

コマンドクラスでの実装サンプルです。

public class HelloCommand implements CommandIF {

	/* (non-Javadoc)
	 * @see jp.zenryoku.sample.lv3.refactor.CommandIF#execute()
	 */
	@Override
	public void execute() {
		// TODO Auto-generated method stub
		System.out.println("名前を入力してください");
      Scanner input = new Scanner(System.in);
      String inStr = input.nextLine();

      if (CheckerUtil.isMandatory(inStr)) {
         // 未入力時の処理
	   } else {
         // 入力ありの時
      }
   }
}

executeメソッドで、入力時のチェック処理を行います。

とりあえずは、こんな感じの実装で使用することを想定して作りました。

ポイント

通常は、newしてから使用するメンバーメソッドを作るのですが、この処理には、インスタンスを作る必要がないので、staticをつけた静的メソッドに行くしています。

詳細

インスタンスを作る必要性に関して「通常」と言う言葉を使いましたが、何かしらの処理を行うのに、準備が必要な時、処理するデータを1クラスに1つ持たせたい場合などにはインスタンスが必要です。

例えば、DBにアクセスする時には、DBサーバーへのコネクションを取得、保持する必要があり、アクセス(データを取得する操作を複数人が行う想定の場合にはstatic(静的)クラスやメソッドは使えません。何故かと言うとstaticをつけると、メソッドの場合は、インスタンス1つにつき1つのクラスにしか影響しなかったのが全てのインスタンスに影響します。

具体的には

リストにデータを設定する場合、リストの中身のデータクラスがstaticだと、リストの中身が全部同じになります。

逆にstaticでない場合は、それぞれのクラスに別々のデータが設定されます。


Java Mid Basic〜数秘術から社会人基礎力のロジック〜

今回は、数秘術の数字を算出する処理を実装します。
参照するのは、こちらの本です。

数秘術の数字

この本によるとまずは「ネームチャート」を作るのがはじめの一歩のようです。「〜ようです」というのは明示的に書いてないのでこのような書き方をしています。

ネームチャート

早い話が、名前をローマ字(アルファベット)にして母音と子音を出し、それに数字を割り振ったものです。
具体的には「Nanashino Takunoji」であれば下のようになります。

Nanashino -> 
母音[ 1, 1, 9, 6] "aaio";= 1+1+9+6=17=>1+7=「8」
子音[5, 5, 1, 8, 5]"Nnshn"= 5+5+1+8+5=24=>2+4=「6」
合計する=>8+6=14=>1+4=「5」

Takunoji -> 
母音[ 1, 3, 6, 9] "auoi";= 1+3+6+9=18=>1+8=「9」
子音[ 2, 2, 5, 1]"Tknj"= 2+2+5+1=10=>「1」
合計する=>9+1=10=>「0」

苗字の部分の計算、名前を計算して結果を出します。
ちなみに最後に算出している数字は「人格数」にあたります。

ロジックにする

「ロジック」と聞いて「は?」となる人のために記載しますが、早い話が、入力に対して想定通りの結果を返せる「処理」のことです。
今回の場合は。。。
入力:「名前(フルネーム)」のアルファベットが入ります。
出力:上で示したように「人格数」となりそうですが、そうではなく、母音と子音の"["と"]"で囲まれた数字を出力として欲しいのです。
この出力結果が「人格数」になると他の「運命数」は「生年月日」なので違いますが、「ハート数」などが算出できません。

いざロジック

初めに上に示したものを作成します。入力が「名前」出力が「母音と子音の数字配列」です。
しかし、Javaのメソッドの返り値は1つだけなので文字列に区切りもの「,(カンマ)」をつけて分けます。。。
いや、面倒なので配列にしてしまいましょう。コードにすると次のようになるのですが、今回は処理の仕様が決まっているので「テストケースから作成します。→「テスト駆動型開発」というやつです。
作成したテストコードもあります。

でわでわ。。。



Java Mid Basic 〜数秘術から社会人基礎力のロジック〜

イントロダクション

前回までに、占い(数秘術)と社会人基礎力の調査を行い、占いから社会人基礎力のパラメータを算出する方法を作成しました。
今度は、それをプログラムで実装しようというわけです。

設計

数秘術の数字の算出方法は占いの手順をそのまま行うので良いのですが、各数字(人格数、運命数。。。)から「社会人基礎力」の算出に関しては、理論も何もないので「直感」で割り振りました。下のような感じになります。詳細は前回の記事に記載しております。

<社会人基礎力>
1:「目的」
2:「学び」
3:「統合」
<ルール>
「人格数」: 1:「目的」と2:「学び」に割り振る
「運命数」: 3:「統合」に割り振る
「ハート数」: 1:「目的」に割り振る
「意思数」: 2:「学び」に割り振る
「成熟数」: 3:「統合」に割り振る

処理手順について

処理手順は前回の記事に記載した、算出方法がそのまま適用されます。
つまり、以下のような手順になります。

手順

  1. 数秘術で各数字(人格数など)を算出
  2. 社会人基礎力への変換表に付け合わせて値を割り振る

しかし、この手順ではプログラムに落とすことができません。視点が人間目線なので機械のレベルまで掘り下げてやる必要があります。
人間目線(高レベル)→機械目線(低レベル)というようなイメージです。そして、世間でよく言われている「高レベルAPI」という言葉は人間目線に近いAPIという意味です。逆にいうと細かい部分の操作ができません(笑)

レベルを下げる

初めの手順ではレベルが高い位置にあるので、これを下げて機械レベル(PCレベル)に近づけていきます。
上の手順を「PC操作でやろうとした時にどのように操作するか?」を考えます。そうすると下のようになると思います。

  1. 数秘術で算出するのに必要な「名前」と「生年月日」を入力する(名前はローマ字 or アルファベット)
  2. 入力されたアルファベット(ローマ字を含む)より、数秘術の計算を行う
  3. 各数字(人格数など)を算出し、社会人基礎力のマップに照らし合わせて、各項目に対しカウントアップ(割り振り)を行う

以上のような手順になります。

プログラムにすると

大雑把に下のような形になります。もちろん他にも方法はありますし、このサンプルが気に入らなければ他のやり方でも結構です。むしろアイディアを聞かせて欲しいくらいです(笑)

とりあえずは、このような形でプログラムを作成していけばゴールにはたどり着けるであろうというものです。

関連ページが下の方にあるのでよかったらどうぞ。

public class CreatePlayerParam implements CommandIF {

    /** 
     * CommandIFを実装する、コマンドクラス。
     * 
     * @see jp.zenryoku.sample.lv3.refactor.CommandIF#execute()
     */
    @Override
    public void execute() {
        // 標準入力
        Scanner input = new Scanner(System.in);
        System.out.println("あなたの名前をローマ字(ヘボン式)で入力してください: ");
        // 入力された文字列
        String inStr = input.nextLine();

        // 母音を切り取る
        String boIn = cutOffBoin(inStr);
        // 子音
        String shiIn = inStr;
        // 人格数
        int jinkakuSu = getJinkakuSu(boIn, shiIn);
        // 運命数
        int unmeSu = getUnmeSu(boIn, shiIn);
        // ハート数
        int heartSu = getHeartSu(boIn, shiIn);
        // 意思数
        int ishiSu = getIshiSu(boIn, shiIn);
        // 成熟数
        int seijukuSu = getSeijukuSu(boIn, shiIn);
        /* 社会人基礎力の算出 */
    }

    public String cutOffBoin(String inStr) {
        // 未実装
        return null;
    }

    public int getJinkakuSu(String boIn, String shiIn) {
        // 未実装
        return 0;
    }

    public int getUnmeSu(String boIn, String shiIn) {
        // 未実装
        return 0;
    }

    public int getHeartSu(String boIn, String shiIn) {
        // 未実装
        return 0;
    }

    public int getIshiSu(String boIn, String shiIn) {
        // 未実装
        return 0;
    }

    public int getSeijukuSu(String boIn, String shiIn) {
        // 未実装
        return 0;
    }
}

以前作成したCommandIFを使用して、このクラスをロードしてパラメータを算出するような実装にしようと思っているので。このクラスのみを作成すれば、今まで作成していたコンソールアプリに組み込むことができます。詳細は上記のリンクを参照してください。

でわでわ。。。

関連ページ

Java Mid Basic テキストRPG 〜数秘術から社会人基礎力を算出〜

イントロダクション

前回は、数秘術についてどんなものか調べました。
今回は「社会人基礎力」を算出する、ルールを作成します。
詳細な内容に関してはこちらの記事に記載しました。
大雑把に、数秘術の書く値が、それぞれどのような意味を持ち、それに対して関連付ける値はどのようなものが良いか?を考えるための材料を揃えた次第です。調査自体はなかなか面白かったし、女性には喜ばれますね。ちょっとだけ相談しました。→「占いって興味ありますか?」的なことを聞きました。
そして、まとめたものが、以下のような表になります。


1:「目的」=>「前に踏み出す力」のために必要とされる

主体性:1、7、9
働きかけ力:3、7、9
実行力:1、5、8

2:「学び」=>「考え抜く力」のために必要とされる

課題発見力:2、4、8
計画力:2、5、8
創造力:1、2、9
3:「統合」=>「チームで働く力」のために必要とされる

発信力:1、4、9
傾聴力:1、7、0
柔軟性:4、6、9
情報把握力:1、7、0
規律性:4、6、0
ストレスコントロール力:1、5、9


算出手順

[Step1]
まずは数秘術にて、以下の数値を算出します。詳細はこちら

  1. 「人格数」: 名前の合計
  2. 「運命数」: 生年月日の合計
  3. 「ハート数」: 母音の合計
  4. 「意思数」: 子音の合計
  5. 「成熟数」: 人格数 + 運命数
  6. 「特性数」:用途未定
  7. 「欠落数」:用途未定

そして、それぞれに算出した値を上記の表に割り当てます。

<例>
「人格数」: 名前の合計=1
「運命数」: 生年月日の合計=2
「ハート数」: 母音の合計=3
「意思数」: 子音の合計=4
「成熟数」: 人格数 + 運命数=3
のような結果が出た人であれば、上記の表と以下のルールより
<ルール>
「人格数」: 1:「目的」と2:「学び」に割り振る
「運命数」: 3:「統合」に割り振る
「ハート数」: 1:「目的」に割り振る
「意思数」: 2:「学び」に割り振る
「成熟数」: 3:「統合」に割り振る

主体性:1
働きかけ力:1
実行力:1
課題発見力:1
計画力:
創造力:1
発信力:1
傾聴力:1
柔軟性:
情報把握力:
規律性:
ストレスコントロール力:

のようになります。これは「先天的に持っている」という程のデータになるのと、ゲームで使用するパラメータなので「なんか点数低くね?」とか思わないでください。。。

でわでわ。。。

関連ページ

  1. テキストRPGを作る〜数秘術の概要まとめ〜
  2. Java Mid Basic〜リファクタリングLv2 処理の整理とクラス分け(準備編)〜
  3. Java Mid Basic 〜Lv3_2_Javaの基本(リファクタリングLv2)ゲームループ付き
  4. Java Mid Basic〜リファクタリングLv2 Mainメソッドを作る〜
  5. Java Mid Basic〜リファクタリングLv2 プロパティファイルで無修正ものを作る〜
  6. Java Mid Basic 〜仕様作成、数秘術から社会人基礎力〜


Java Mid Basic 〜仕様作成、数秘術から社会人基礎力〜

イントロダクション

こちらのサイトを見つけました、内容は、「社会人基礎力」について項目を挙げ、各内容について記載している記事です。丁度、ゲームのパラメータに悩んでいたところヒトとしてのパラメータ的なものがあったので、参考にしたました。
使用するのは以下のものです。

<数秘術>; 下のリンクは広告です。

経済産業省提唱「社会人基礎力

換算する値

以下の項目が、「社会人基礎力」で挙げられている項目です。12個あります。カテゴリとしては、1〜3=①, 4〜6=②, 7〜12=③の様に分けます。

<パラメータ>

  1. 主体性: 1, 7, 9
  1. 働きかけ力:3, 7, 9
  1. 実行力:1, 5, 8
  1. 課題発見力:2, 4, 8
  1. 計画力:2, 5, 8
  1. 創造力:1, 2, 9
  1. 発信力:1, 4, 9
  1. 傾聴力:1, 7, 0
  1. 柔軟性:4, 6, 9
  1. 規律性:4, 6, 0
  1. ストレスコントロール:1, 5, 9
  1. 状態把握力:1, 7, 0

これらの項目に値を設定するのですが、結論から言うと計測不能なので直感で割振りました。数秘術で算出した以下の項目を使います。

数秘術の値

  • 人格数=①と②に割り振る
  • 運命数=③に割り振る
  • ハート数=①
  • 意思数=②
  • 成熟数=③
  • 特性数=保有属性
  • 欠落数=弱点属性

上の値は、<パラメータ>の表にある数字部をカウントする形で算出します。

人格数が9の人は、表の番号1, 2, 6に一点加算するといった形で計算していきます。

数字について

数字; 属性 / 星; 神様
1: 火 / 太陽; ヘリオス, アポロン )
2: 水 / 月; セレネー, アルテミス
3: 風 / 土星; クロノス, サトゥルヌス
4: 地 / 地球: ガイア
5: (エーテル)風 / 水星; ヘルメス
6:地/ 金星; アプロディーテー, ヴィーナス)
7: 風 / 海王星; ネプチューン, ポセイドン
8: 水 / 土星; クロノス, サトゥルヌス
9: 火 / 火星; アレス

これらの数字は、「特製数」と「欠落数」に対応させようと考えております。

特性数

1、3、4が特性数の場合→火、風、地の属性を持ちます。そして、ネームチャートに出て来た数字の数をレベルにします。つまり「A」が2回、「C」が1回、「D」が3回出てくるような人は火=Lv2, 風=Lv1, 地=Lv3となります。

欠落数

逆に2、5、8が欠落数の場合は水、(エーテル)風が弱点になり、2と8は属性が「水」なので水が致命的な弱点になります。

とりあえず

現状ではこんな感じの仕様でゲーム作成を行おうと思います。

でわでわ。。。



Java Mid Basic〜Lv3_4_リファクタリングLv2 プロパティファイルで無修正ものを作る〜

イントロダクション

前回は、メインメソッドの修正を行わないでのコマンド追加実装をやりました。
具体的には、コンストラクタに作成した、クラスをマップに登録する処理を追加します(1行)。。。
しかし。これもなんだかんだとクラスの修正をしているのでイマイチです。なので

メインクラスを修正しない

この方向でコマンドを追加する方法を実践したいと思います。その方法は以下の手順で行います。

  1. プロパティファイルにキーとクラスの完全名をセットにして登録する
  2. クラスの完全名からクラスのインスタンスを取得して実行するようにする

上記のような形になります。「よくわからん。。。」と思った方、詳細を下に記載します。

修正のいらない実装

修正をしなくても「クラスを追加」もしくは、「新しい機能の実装を追加」するためには「依存関係を分断」してやる必要があります。

依存関係の分断

依存関係とは「関連性がある」ということ、つまり「関連性がある=片方を修正したら、もう片方も修正する必要がある」という関係が出来上がっていることです。
よく世間では「依存関係の注入」などという言葉で言いますが、実際どういうことか?に関して触れていることが少ないように感じています。
実装方法に関してはいろんな記事があり、わかりやすく説明がされていると思います。

つまり

依存関係を分断しようということです。具体的には下のように考えます。

  1. 新しい機能を追加するときには今実装している部分を修正したくない
  2. 新しく機能を追加するのと、実装中のクラスを修正しない方法は?

大雑把に、上のように考えます。サンプルとして今回のサンプルアプリを参考にして考えてみます。
現状の実装はGitにあるソースを見てもらうと一目瞭然です、一部抜粋して以下に示します。(メインメソッドのみ抜粋)

public static void main(String[] args) {
    // コマンドの用意
    cmdMap = new HashMap();
    cmdMap.put("hello", new HelloCommand());
    cmdMap.put("ready", new ReadyCommand());
    // 標準入力
    Scanner input = new Scanner(System.in);

    while(true) {
        System.out.println("入力してください: ");
        String inStr = input.nextLine();
        CommandIF cmd = cmdMap.get(inStr);
        if (cmd != null) {
            cmd.execute();
        }
        if ("bye".equals(inStr)) {
            System.out.println("Good Byw");
            break;
        }
    }
}

動きは下の動画のようになります。

この状態では、Mapにコマンドクラスを追加してやれば新しい機能を追加できます。なので修正するポイントはコンストラクタのみになります。

この際だから。。。

そう、「ここまできたら最後までいけ!」と思うのが人情(自分だけでしょうか?(笑))、なのでやってしまいましょう。

<実装手順>

  1. コンストラクタでMapにコマンドクラスを追加するのをやめる
  2. その代わりにプロパティファイルを使用する
  3. プロパティファイルから完全クラス名を取得し、インスタンスを生成
  4. 生成したインスタンスのメソッドを実行する

このようにしてやれば、メインクラスの修正はいりません。
<追加するもの>
・プロパティファイル
・プロパティファイルのロード処理
・同様に値の取得処理

[CommandList.properties]
 hello=jp.zenryoku.sample.lv3.refactor.cmd.HelloCommand
ready=jp.zenryoku.sample.lv3.refactor.cmd.ReadyCommand
/** プロパティファイル取得 */
public void loadPropertyFile() {
    prop = new Properties();
    try {
        // resources/
        prop.load(getClass().getResourceAsStream("/CommandList.properties"));
    } catch (IOException e) {
        e.printStackTrace();
        // エラーコード-1をセットしてプログラム終了
        System.exit(-1);
    }
}

プロパティファイルからインスタンスの取得

public CommandIF getCommandIF(String key) {
    // 完全クラス名を取得する
    String fullClassName = prop.getProperty(key);
    if (fullClassName == null || "".equals(fullClassName)) {
        return null;
    }
    CommandIF cmd = null;
    try {
        @SuppressWarnings("unchecked")
        Class cmdCls = (Class) Class.forName(fullClassName);
        cmd = cmdCls.newInstance();
    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        System.exit(-1);
    }
    return cmd;
}

このように、メインメソッドの修正を行わなくてもCommandIFを実装したクラスの数だけコマンドに対する実行処理を作成することができます。

その代わり、プロパティファイルにキーと値を追加する必要がありますが。。。しかしコードを修正するとテストを行う必要があるので格段に余計な手間と時間を省くことができます。

まさに「無修正」

エロいですねぇ(笑)

できた時間を使用して、心と体のリフレッシュ及び、楽しい時間を過ごす。。。これが平和への第一歩だと思います。
このように、時間を大切に使わないと、昔の不況時代のように「寝る暇がない!」とか「今日も漫画喫茶でお泊まり!」とか。。。おおよそ人間の尊厳が無いような、不幸なことになってしまうので、日々精進を重ねることが大切だと思います。。。趣味にしてしまうのが一番手っ取り早いという噂もあるとか無いとか(笑)

でわでわ。。。



Java Mid Basic〜Lv3_3_リファクタリングLv2 Mainメソッドを作る〜

イントロダクション

初めに、メインメソッドを整理して、オブジェクト指向プログラミングの準備を行いました。そして、次にインターフェースの使い方について記載と実装を行いました。

<早い話>
クラスを使用して、役割分担をしたいけど。。。処理がごちゃ混ぜになっていると分担しづらいので整理して、どこでどんな処理をしているかわかりやすくしました。

インターフェースを使う

今回からは、1クラス1機能の形で実装していきたいと思います。

なので、複数クラスを使用していきますので、あっちこっち飛ぶことになります。

まずはメインメソッドのクラスを作成します。名前は「Lv3_1_RefactorLv2_Main」にします。
そして、今までやってきたものとほぼ同じものを作成します。
<実装内容>
・標準入力からコマンドを実行する
・「hello」コマンドで「Hello World」をコンソールに表示
・「ready」コマンドで「Ready?」をコンソールに表示
・1クラス1機能

上記のような形で実装します。
<実装するクラス>
Lv3_1_RefactorLv2_Main「処理フローを作る」
CommandIF「コマンドを作る」
HelloCommand「helloコマンド」
ReadyCommand「readyコマンド」

上記の4クラスを作成します。
ここで、インターフェースの使い方を復習します。以前記載したインターフェースの使い方も参照ください
ちなみに、インターフェースの作り方(Eclipseでの作成方法)に関しては下の動画を参照ください。

インターフェースの使い方

インターフェースは、実装部分のないメソッドの宣言のみが実装されているクラスのことです。具体的には下のようなコードになります。

/**
 * コマンドインターフェース
* インターフェースは、処理内容を書きません。 * * @author takunoji * 2019/08/23 */ public interface CommandIF { /** コマンドを実行する */ public void execute(); }

インターフェースクラスの名前は「CommandIF」で実行するメソッドは「execute()」です。このインターフェースを実装(Implements)するクラスは「execute()」メソッドを実装する義務が生じます。つまり実装しないとビルドエラーになります。

public class HelloCommand implements CommandIF {

    /* (non-Javadoc)
     * @see jp.zenryoku.sample.lv3.refactor.CommandIF#execute()
     */
    @Override
    public void execute() {
        // TODO Auto-generated method stub
        System.out.println("Hello World!");
    }
}

オーバーライド

ちなみに「@Override」はオーバーライド、メソッドのオーバーライドのことを意味しています。オーバーライドの使い方は大雑把に2種類あります。
<パターン1: Interfaceのオーバーライド>
上のコードそのままです。
<パターン2: 実装されているものをオーバーライド>
Javaでの自作クラスを含む全てのクラスは「Objectクラス」を親クラスに持ちます。親クラスのことを「スーパークラス」と呼び、こちらが世界でも通用する言い方になります。具体的には、以下のようなクラスを作成したとします。

public class ReadyCommand /* implements CommandIF */ {

    /* (non-Javadoc)
     * @see jp.zenryoku.sample.lv3.refactor.CommandIF#execute()
     */
    /* @Override */
    public void execute() {
        // TODO Auto-generated method stub
        System.out.println("Ready?");
    }
}

本当は、CommandIFを実装しているクラスなのですが、説明のため。。。

オーバーライド2

このクラスは「スーパークラス」(super class)にObjectクラスを持っています。なので、Objectクラスのメソッドも使用することができます。その証拠にEclipseなどのIDEを使用してコードアシスト機能などで使用できるメソッドが選択できることがありますが、これはObjectクラスのメソッドです。(下のイメージ参照)

そして、この「equals()」メソッドをオーバーライドすると下のような実装になります。

public class ReadyCommand implements CommandIF {

    /* (non-Javadoc)
     * @see jp.zenryoku.sample.lv3.refactor.CommandIF#execute()
     */
    @Override
    public void execute() {
        // TODO Auto-generated method stub
        System.out.println("Ready?");
    }

    @Override
    public boolean equals(Object cls) {
        return true;
    }
}

インターフェースを使う

これに関しては、詳細の説明よりも動かしてみるのが一番だと思うので(実装してみるのがベスト)、実行結果を舌に載せておきます。

このように、作成したコマンドクラスを登録してやるだけで新しい処理を追加できます。しかしこれではMainメソッドの修正が必要になるのでイマイチです。

次回は、メインメソッドを修正しなくても良いようにリファクタリングします。

でわでわ。。。



Java Mid Basic 〜Lv3_2_Javaの基本(リファクタリングLv2)ゲームループ付き

イントロダクション

前回は、リファクタリングをするのに「オブジェクト指向」について少し触れました。世間巷でいう「オブジェクト指向」というものとちょっと内容が違うように感じた人もいるかもしれません。がオブジェクト指向はこのように考えます。

  1. もの(オブジェクト)を大切にしましょう
  2. 再利用できるものは再利用しましょう
  3. 1クラスに1機能

ここで「つまり。。。」というのを考える必要があります。

オヴジェクト指向

ちょっと、寄り道をしてオブジェクト指向的なプログラミングのサンプルを作成したいと思います。「百聞は一見にしかず」といったところでしょうか(笑)

まずはメインメソッド

とりあえずは、メインメソッドを作成します。そして、標準入力を受けて無限ループする処理を作成します。さらに、「bye」と入力すると処理(アプリ)を終了する。という処理にします。まとめると以下のようになります。

  1. 標準入力を受ける
  2. "bye"と入力すると処理を終了する

これは、メインメソッドを定義するためのいわば「メインクラス」とします。

public class Lv3_1_RefactorLv2_Main {

    public static void main(String[] args) {
        // 標準入力
        Scanner input = new Scanner(System.in);

        while(true) {
            System.out.println("入力してください: ");
            String inStr = input.nextLine();
            if ("bye".equals(inStr)) {
                System.out.println("Good Byw");
                break;
            }
        }
    }
}
コマンド実行する

ここで「コマンド」を入力した時は、そのコマンドを実行するように作成します。イメージは下のような感じです。

上に記載したクラスが、メインクラスですが、まだコマンドマップを設定(コーディング)していません。次のようなコードをフィールドに追加します。private static List<CommandIF> cmdMap;メインメソッドで呼び出すために、「static」修飾子をつけます。そして、実装結果が下のコードになります。

public class Lv3_1_RefactorLv2_Main {
    /** コマンドマップ */
    private static  Map cmdMap;
    /** メインメソッド */
    public static void main(String[] args) {
        // 標準入力
        Scanner input = new Scanner(System.in);

        while(true) {
            System.out.println("入力してください: ");
            String inStr = input.nextLine();
            if ("bye".equals(inStr)) {
                System.out.println("Good Byw");
                break;
            }
        }
    }
}

ビルドエラーが!

そうなんです。このままだとビルドエラーが出ます。
存在しないクラス(インターフェースクラス)が使用されていればビルドエラーが出ます。なので作ります。

CommandIFインターフェースを作成

インターフェースの作成方法に関してはこちらの記事もご覧ください。インターフェースクラス、抽象クラスの作成方法に関して記載しています。
作成したものは下のようになります。

/**
 * コマンドインターフェース
* インターフェースは、処理内容を書きません。 * * @author takunoji * 2019/08/23 */ public interface CommandIF { /** コマンドを実行する */ public void execute(); }

インタフェースはメソッドの宣言のみなので、作るのは簡単なのです(笑)

そして、コマンドを設定するためのマップをインスタンス化します。

public class Lv3_1_RefactorLv2_Main {
    /** コマンドマップ */
    private static  Map cmdMap;
    public static void main(String[] args) {
        // コマンドの用意
        cmdMap = new HashMap();
        // 標準入力
        Scanner input = new Scanner(System.in);

        while(true) {
            System.out.println("入力してください: ");
            String inStr = input.nextLine();
            if ("bye".equals(inStr)) {
                System.out.println("Good Byw");
                break;
            }
        }
    }
}

インターフェースの実相

ここまできたらあとは、インターフェースの実相クラスを作成するだけです。
今回は、helloコマンドとreadyコマンドを作成します。どちらもコンソール出力するだけのシンプルなコマンドです。
helloコマンドは「HelloCommand」クラス、readyコマンドは「ReadyCommand」クラスに対応させます。
HelloCommand

public class HelloCommand implements CommandIF {

    /* (non-Javadoc)
     * @see jp.zenryoku.sample.lv3.refactor.CommandIF#execute()
     */
    @Override
    public void execute() {
        // TODO Auto-generated method stub
        System.out.println("Hello World!");
    }
}

ReadyCommand

public class ReadyCommand implements CommandIF {

    /* (non-Javadoc)
     * @see jp.zenryoku.sample.lv3.refactor.CommandIF#execute()
     */
    @Override
    public void execute() {
        // TODO Auto-generated method stub
        System.out.println("Ready?");
    }
}

どちらも、Eclipseでインターフェースのメソッドを自動生成したので、TODOコメントがついています。Eclipseの設定で出力しないように変更もできるので余裕があれば調べて見てください。

実装と実行結果

下のようになりました。

public class Lv3_1_RefactorLv2_Main {
    /** コマンドリスト */
    private static  Map cmdMap;
    public static void main(String[] args) {
        // コマンドの用意
        cmdMap = new HashMap();
        cmdMap.put("hello", new HelloCommand());
        cmdMap.put("ready", new ReadyCommand());
        // 標準入力
        Scanner input = new Scanner(System.in);

        while(true) {
            System.out.println("入力してください: ");
            String inStr = input.nextLine();
            CommandIF cmd = cmdMap.get(inStr);
            if (cmd != null) {
                cmd.execute();
            }
            if ("bye".equals(inStr)) {
                System.out.println("Good Byw");
                break;
            }
        }
    }
}

初めの実装とほぼ変わらないと思います。
ちょいと実行して見ます。

こんな感じで動きます。
でわでわ。。。



Java Mid Basic〜リファクタリングLv2 処理の整理とクラス分け(準備編)〜

イントロダクション

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

クラス分け

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

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

どーやるか?

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

基本ワザ?

基本ワザとは以下のような実装方法のことです。

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

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

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

概要

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

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

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

<手順>

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

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

リファクタリング開始

元のコードは、こちらのコードです。前回作成した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
でわでわ。。。



Java Mid Basic〜リファクタリングLv2構成を考える〜

イントロダクション

前回までに、UMLの書き方などを理解しました。使用しているUMLツールは「Star UML」です。
プログラムを書くのにUMLを使って「どのようなクラス構成にするか?」を考えます。
まずは、現状のクラス構成を図にしてみます。「現状」というのは作成中のコンソールゲーム(学習用)です。

現状のクラス図

動きとしては、下の動画のように動きます。

<仕様>

  • ・アプリが起動したらユーザーの入力を待つ
  • ・入力(コマンド)によりコンソール出力する内容を切り替える
  • ・hello->"Hello World"
  • ・計算式(「1 + 2」など)->計算結果
  • ・bye->実行したコマンドの一覧を出力してアプリ終了
  • ・title->テキストファイルの中身を出力
  • ・上記以外->"想定外の入力です"を出力し、次のコマンド入力を待機

クラス図にすると下のような形になります。

ここから、どのようにクラス構成を作れば良いか考えます。

リファクタリング

「基本的」というのは「土台になる」という意味で使用しています。そして、今回のリファクタリングを行うの時の
基本的な考え方は以下の通りです。

  1. 1機能につき、1クラス
  2. 作成するクラス(機能)は再利用できるように作る
  3. JavaDocなどを使用して、新規でコードを見る人にもわかるようにコーディングする

現状の問題点

クラスが1つだけなので、大雑把に4機能(上の画像内の<>で囲っているもの)がこのクラスの中にあり「ごちゃー」としていて、まるで汚い部屋のようです。
まずは整理整頓ですね、そして、クラスを複数作成するのでこの部分クラスとクラスの繋がり部分をうまく作成してやる必要があります。

さらなるアイディア

整理整頓を行うついでに「こーすれば!」というものも組み込むともっと良いと思います。

関連ページ

UMLツール Star UMLを使う〜

UMLツール Star UML〜ユースケース図を書いて見た〜