Fizz Buzz問題とSingleton パターン


#問題

Fizz Buzz問題というプログラミングの練習問題があります。番号に従い、以下のように返事をするという問題です。

|  番号i        |  返事      |
|:--------------|:-----------|
| 3かつ5の倍数  |  FizzBuzz  |
| 3の倍数       |  Fizz      |
| 5の倍数       |  Buzz      |
| 上記以外      |  i         |

実際のプログラミングでは番号iをfor文で使うと思います。例えばiが1から50まで繰り返すという具合です。この記事ではiの繰り返しについて、開始番号と終了番号を入力して、Fizz Buzzを答えるという状況を考えます。このとき、開始番号と終了番号の履歴機能を持たせるという問題を考えます。履歴は10件とします。

#プログラミング

##履歴機能

一例として、履歴機能にSingleton パターンを適用しました。

以下は、Fizz Buzzを答えるクラスです。staticメソッドを一つ持ちます。

```java:FizzBuzz
package fizzbuzz;

public class FizzBuzz {

    private FizzBuzz() {}

    public static String replyBasedOn(int i) {

        String s = null;

        if (i % 3 == 0 && i % 5 == 0) {
            s = "FizzBuzz";
        } else if (i % 3 == 0) {
            s = "Fizz";
        } else if (i % 5 == 0) {
            s = "Buzz";
        } else {
            s = String.valueOf(i);
        }

        return s;

    }

}
```

以下は、履歴機能を持つクラスです。getInstance()メソッドがインスタンスを返却します。LinkedList<Integer>型の変数historyStartとhistoryEndが履歴を保存するための変数です。

```java:FizzBuzzHistory.java
package fizzbuzz;

import java.util.LinkedList;

public class FizzBuzzHistory {

    private FizzBuzzHistory() {}

    private static class FizzBuzzHistoryHolder {
        private static final FizzBuzzHistory instance = new FizzBuzzHistory();
    }

    public static FizzBuzzHistory getInstance() {
        return FizzBuzzHistoryHolder.instance;
    }

    LinkedList<Integer> historyStart = new LinkedList<>();
    LinkedList<Integer> historyEnd = new LinkedList<>();

    public void offer(int start, int end) {

        if (historyStart.size() == 10) {
            historyStart.remove();
            historyEnd.remove();
        }

        historyStart.offer(start);
        historyEnd.offer(end);

    }

    public Integer[] getStarts() {
        return historyStart.toArray(new Integer[0]);
    }

    public Integer[] getEnds() {
        return historyEnd.toArray(new Integer[0]);
    }

}
```

##JavaFXで動かす

実際に動かす部分はFavaFXを使いました。以下は画面です。ボタンを押すと、テキストエリアにFizzBuzzと履歴を表示します。

![view.png](https://qiita-image-store.s3.amazonaws.com/0/133921/60f1a62e-0d2a-21cb-dc4f-6843fad745b4.png)


以下、Main.java、app.fxml、AppController.javaです。

```Main.java
package application;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) {
        BorderPane root;
        try {
            root = (BorderPane)FXMLLoader.load(getClass().getResource("app.fxml"));
            Scene scene = new Scene(root);
            primaryStage.setTitle("FizzBuzz");
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
```

```xml:app.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.text.Font?>

<BorderPane xmlns="http://javafx.com/javafx/" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.AppController">
   <center>
      <Pane prefHeight="300.0" prefWidth="300.0" BorderPane.alignment="CENTER">
         <children>
            <Label layoutX="85.0" layoutY="15.0" text="開始" />
            <Label layoutX="85.0" layoutY="50.0" text="終了" />
            <TextField fx:id="textFieldOfStart" layoutX="120.0" layoutY="10.0" promptText="開始番号" text="1" />
            <TextField fx:id="textFieldOfEnd" layoutX="120.0" layoutY="50.0" promptText="終了番号" text="15" />
            <Button layoutX="15.0" layoutY="10.0" mnemonicParsing="false" onAction="#output" text="出力" />
            <Label layoutX="15.0" layoutY="120.0" text="FizzBuzz" />
            <TextArea fx:id="textAreaOfFizzBuzz" editable="false" layoutX="66.0" layoutY="119.0" prefHeight="76.0" prefWidth="200.0">
               <font>
                  <Font name="Monospaced Regular" size="12.0" />
               </font></TextArea>
            <Label layoutX="35.0" layoutY="200.0" text="履歴" />
            <TextArea fx:id="textAreaOfFizzBuzzHistory" editable="false" layoutX="65.0" layoutY="200.0" prefHeight="90.0" prefWidth="200.0">
               <font>
                  <Font name="Monospaced Regular" size="12.0" />
               </font></TextArea>
            <Separator layoutX="15.0" layoutY="90.0" prefHeight="15.0" prefWidth="250.0" />
         </children>
      </Pane>
   </center>
</BorderPane>
```

```java:AppController.java
package application;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import fizzbuzz.FizzBuzz;
import fizzbuzz.FizzBuzzHistory;

public class AppController {

    @FXML TextField textFieldOfStart;
    @FXML TextField textFieldOfEnd;

    @FXML TextArea textAreaOfFizzBuzz;
    @FXML TextArea textAreaOfFizzBuzzHistory;

    @FXML
    public void output(ActionEvent event) throws Exception {

        // 値受け取り
        int start, end;
        try {
            start = Integer.parseInt(textFieldOfStart.getText());
        } catch (NumberFormatException e) {
            showDialog("開始の入力が不正です。");
            return ;
        }
        try {
            end = Integer.parseInt(textFieldOfEnd.getText());
        } catch (NumberFormatException e) {
            showDialog("終了の入力が不正です。");
            return ;
        }


        // 正数かチェック
        if (start <= 0 || end <= 0) {
            showDialog("正数を入力してください。");
            return ;
        }

        // 相関チェック
        if (end < start) {
            showDialog("開始が終了より後です。");
            return ;
        }

        // FizzBuzz出力
        String lineSeparetor = System.getProperty("line.separator");
        StringBuilder sb = new StringBuilder();
        for (int i = start; i <= end; i++) {
            sb.append(FizzBuzz.replyBasedOn(i));
            sb.append(lineSeparetor);
        }
        textAreaOfFizzBuzz.setText(sb.toString());

        // 履歴保存
        FizzBuzzHistory history = FizzBuzzHistory.getInstance();
        history.offer(start, end);

        // 履歴出力
        Integer[] historyStarts = history.getStarts();
        Integer[] historyEnds   = history.getEnds();
        sb = new StringBuilder();
        for (int i = 0; i < historyStarts.length; i++) {
            sb.append(String.format("%2d: ", i+1));
            sb.append("開始: ");
            sb.append(historyStarts[i]);
            sb.append(", 終了: ");
            sb.append(historyEnds[i]);
            sb.append(lineSeparetor);
        }
        textAreaOfFizzBuzzHistory.setText(sb.toString());

    }

    private void showDialog(String contentText) {

        Alert alert = new Alert(AlertType.INFORMATION);
        alert.getDialogPane().setHeaderText(null);
        alert.getDialogPane().setContentText(contentText);
        alert.showAndWait();

    }

}
```

ボタンを押すとoutput()メソッドが動きます。そのメソッドの中でFizzBuzzHistory.getInstance()メソッドを呼び出してます。クラスの作り方から、一回目のボタン押下時のみFizzBuzzHistoryのインスタンスが生成されます。