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での作成方法)に関しては下の動画を参照ください。

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

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

/**
 * コマンドインターフェース<br/>
 * インターフェースは、処理内容を書きません。
 * 
 * @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;
    }
}

クラスの継承

下の図はクラス図で表現したときの継承を表す図です。「extends Parent」としたときのものです。親クラスのメソッドを子クラスで同じように書けばそれが「オーバーライド」になります。簡単ですが、オーバーライドの補足でした。


インターフェースを使う

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

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

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

でわでわ。。。

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

イントロダクション

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

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

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

オヴジェクト指向プログラミング

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

<基本的なところを動画にしました。基本文法などです。>

<インターフェースの使い方例>

<インターフェースの使い方2:CommandIFを使う>

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;
            }
        }
    }
}

2.コマンド実行する

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

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

public class Lv3_1_RefactorLv2_Main {
    /** コマンドマップ */
    private static  Map⁢String, CommandIF> 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<String, CommandIF> cmdMap;
    public static void main(String[] args) {
        // コマンドの用意
        cmdMap = new HashMap<String, CommandIF>();
        // 標準入力
        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;
            }
        }
    }
}

3.インターフェースの実装

ここまできたらあとは、インターフェースの実相クラスを作成するだけです。
今回は、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の設定で出力しないように変更もできるので余裕があれば調べて見てください。

Mapインターフェースの実装サンプル動画です。

4.実装と実行結果

下のようになりました。

public class Lv3_1_RefactorLv2_Main {
    /** コマンドリスト */
    private static  Map<String, CommandIF> cmdMap;
    public static void main(String[] args) {
        // コマンドの用意
        cmdMap = new HashMap<String, CommandIF>();
        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;
            }
        }
    }
}

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

こんな感じで動きます。

ポリモーフィズム

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

でわでわ。。。

<<< 前回 次回 >>>

補足その1:リファクタリングの考え方
補足その2:クラスの役割分担を行う

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
でわでわ。。。

<<< 前回 次回 >>>

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〜ユースケース図を書いて見た〜



Java Mid Basic 〜Lv2_8_Javaの基本(Mapを使う)ゲームループ付き〜

イントロダクション

前回は、ファイルの読み込みを行いました。
こんな感じで動きました。

Mapを使う

今回は、Mapを使用します。java.util.Listと同じようにインターフェースです。このMapインターフェースは実装クラスにHashMapがあります。詳細はリンク先の「既知のすべての実装クラス」を見てください。
そして、今回はこのMap(HashMap)を使って、ゲーム上のマップ(目には見えない)を表現することを考えたいと思います。

目に見えないマップ

通常、ゲームのマップといえばキャラクターが動いて「〜の街に入る」とかの動きをするのですが、今回はテキストRPGを作ろうとしているので、その部分を表示しません。全て「プレーヤーの想像」に映し出せるようになんとか「文章」を考える必要があります。そしてBGMも大切です。。。が、まずは「Javaを理解する」を目的としているのでプログラムの方を優先します。
「目に見えないマップ」とは、プログラムの処理上では存在しているけど、ユーザー(プレイヤー)は見ることができない。。。という意味です。
例えば、ユーザーにDBからデータを取得するためのSQLを見る必要はないし、機密情報でもあるので画面には表示しません。
同様に、今回のコンソールゲームでは、なるべくプレーヤーの想像の中に「画面」を描画してもらえるように作成するので、目に見える マップは邪魔以外の何物でもないのです。(そういうコンセプトなのです(笑))

実行結果

処理内容

作成したコードはGitにあります。

/** Mapインターフェース */
private Map<String, String> placeInfo;

/** 現在位置の情報を返却する */
public void showPositionInfo(String pos) {
    if (placeInfo == null) {
        System.out.println("現在位置の情報が設定されていません。(placeInfo = null)");
        System.exit(-1);
    }
    // 入力が「move 数字」になっていない場合はエラー
    if (pos.matches("move [0-9]") == false) {
        System.out.println("入力コマンドが不適切です。move [0-9]で入力してください");
        System.exit(-1);
    }
    // から文字を削除
    String place = pos.replaceAll("move", "");
    // moveを削除
    place = place.replaceAll(" ", "");
    System.out.println(placeInfo.get(place));
}

【前提】
入力値=引数のString posには「move XX」という数字が入ってきます。

初めのif (placeInfo == null) {はフィールド変数にインスタンスが設定されていない場合の例外対応です。想定外の場合というのを考慮に入れています。が他の部分など、全対的にどのような流れで処理を行うのか考えていない状態なので、まぁ、勘弁してください。
そして、コマンドが「move 1」のように「1」の部分が数字で入力されている場合この時にMapに登録されている値を表示します。
ちなみにMapに登録する処理は以下のコードで実装しています。

/** 現在位置の情報を保持するMapを作成 */
public Map<String, String> getInfoMap() {
    HashMap<String, String> map = new HashMap<>();
    map.put("1", "マス1");
    map.put("2", "マス2");
    map.put("3", "マス3");
    map.put("4", "マス4");
    return map;
}

HashMapのインスタンスを生成して、”1”のキーには値: "マス1"を設定します。同様に2〜4まで設定したところで設定処理を終了しています。
プログラムの作成は今までにやってきているので、下の「関連ページ」を参照してください。

でわでわ。。。

<<< 前回 次回 >>>

関連ページ