概要
Java入門で開発するコンソールアプリでは、メニューを表示し、標準入力で受け取った番号の操作を実行するものがあります。
[メニューを選択してください]
1 表示
2 追加
3 戻る
input > 1
みたいなやつです。
これをSwitch文で分岐させる、というのがよくある設計ですが、抽象メソッドと列挙子を活用して実装してみましょう。
実装
まずはメニューを記述する列挙子です。
MemberMenu.java
public enum MemberMenu {
SHOW("1", "表示") {
@Override
public void execute() {
MemberList.getMember()
.stream()
.sorted(Comparator.comparing(Member::getId))
.forEach(m -> System.out.println(m.show()));
}
},
ADD("2", "追加") {
@Override
public void execute() {
new AddMember().execute();
}
},
// 中略
;
// メニュー番号
private final String menuNum;
// メニュー名
private final String label;
/** コンストラクタ */
private MemberMenu(String menuNum, String label) {
this.menuNum = menuNum;
this.label = label;
}
/** 各メニューでの動作を記述するメソッド */
public abstract void execute();
/** 存在するメニュー番号かどうか判定 */
public boolean containsMenu(String target) {
return Arrays
.stream(values())
.anyMatch(r -> r.getMenuNum().equals(target));
}
/** メニュー番号から逆引き */
public static MemberMenu valueOf(String num) {
return Arrays
.stream(values())
.filter(s -> s.getMenuNum.equals(num))
.findFirst();
}
// [番号 名前]の形式にする
public String toString() {
return menuNum + " " + label;
}
// 全メニューをメニュー番号順にソートして、改行で繋げる
public String toMenu() {
return Arrays
.stream(values())
.sorted(Comparator.comparing(s -> s.getMenuNum()))
.map(s -> s.toString())
.collect(Collectors.joining(System.lineSeparator()));
}
}
抽象メソッド execute()
を使い、各メニューでの動作を記述しました。
続いてこれを呼び出す部分です。
MemberView.java
public class MemberView {
public void execute() {
String menuNum;
// 存在するメニュー番号が入力されるまで繰り返し
do {
System.out.println("[メニューを選択してください]");
// メニュー表示
System.out.println(MemberMenu.toMenu());
// 標準入力から取得
menuNum = ScannerManager.getInstance().getScanner().nextLine();
} while(!containsMenu(menuNum));
// 選ばれたメニューを実行
MemberMenu.valueOf(menuNum).execute();
}
}
このように記述すれば、呼び出し元で分岐せずとも、呼びたいメニューが実行できます。
他のクラスは適当に補間してください。
列挙子クラスの共通インターフェース利用
メニューが多数あるシステムの場合、同じ処理が重複するのは好ましくないかと思います。共通部分をインターフェースに記述する方法を記載しておきます。
Ienum.java
/**
* Enum用インターフェース
*/
public interface Ienum<E extends Enum<E>> {
/** コードを取得 */
public String getCode();
/** ラベルを取得 */
public String getLabel();
/**
* Enumに変換
* @return
*/
@SuppressWarnings("unchecked")
default E toEnum() {
return (E) this;
}
/**
* コードが含まれているかを判定するメソッド
* @param code
* @return 判定結果
* <ul>
* <li>含まれている場合 - true</li>
* <li>含まれていない場合 - false</li>
* </ul>
*/
public static <E extends Enum<E>> boolean containsCode(Class<? extends Ienum<E>> enumClass, String code) {
return Arrays
.stream(enumClass.getEnumConstants())
.anyMatch(s -> s.getCode().equals(code));
}
/**
* コードから逆引きするメソッド
* @param <E> 検索対象のenum
* @param enumClass 検索対象のenum
* @param code 検索するコード
* @return 逆引き結果
* <ul>
* <li>存在する場合 - 逆引き結果</dd>
* <li>存在しない場合 - IllegalArgumentException</li>
* </ul>
*/
public static <E extends Enum<E>> E getByCode(Class<? extends Ienum<E>> enumClass, String code) {
return Arrays
.stream(enumClass.getEnumConstants())
.filter(s -> s.getCode().equals(code))
.map(Ienum::toEnum)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException());
}
/**
* 一覧生成
* @param <E> 対象のEnum
* @param enumClass 対象のEnum
* @return メニューを改行で繋いだ文字列
*/
public static <E extends Enum<E>> String toMenu(Class<? extends Ienum<E>> enumClass) {
return Arrays
.stream(enumClass.getEnumConstants())
.sorted(Comparator.comparing(s -> s.getCode()))
.map(s -> s.getCode() + " " + s.getLabel())
.collect(Collectors.joining(System.lineSeparator()));
}
}
上のインターフェースを継承させたメニューの列挙子は以下です。
MemberMenu.java
public enum MemberMenu implements Ienum<MemberMenu> {
SHOW("1", "表示") {
@Override
public void execute() {
MemberList.getMember()
.stream()
.sorted(Comparator.comparing(Member::getId))
.forEach(m -> System.out.println(m.show()));
}
},
;
/** メニューコード */
private String code;
/** メニュー名 */
private String label;
public abstract void execute();
/**
* コンストラクタ
* @param code メニューコード
* @param label メニュー名
*/
MemberMenu(String code, String label) {
this.code = code;
this.label = label;
}
@Override
public String getCode() {
return code;
}
@Override
public String getLabel() {
return label;
}
}
最後にこれの利用方法です。
MemberView.java
public class MemberView {
public void execute() {
String menuNum;
// 存在するメニュー番号が入力されるまで繰り返し
do {
System.out.println("[メニューを選択してください]");
// メニュー表示
System.out.println(Ienum.toMenu(MemberMenu.class));
// 標準入力から取得
menuNum = ScannerManager.getInstance().getScanner().nextLine();
} while(!Imenu.containsMenu(MemberMenu.class, menuNum));
// 選ばれたメニューを実行
Ienum.getByCode(MemberMenu.class, menuNum).execute();
}
}
まとめ
Enumでそれぞれに処理を記述することで、条件と処理をまとめることが出来ます。
これによって複雑な分岐を記述する手間も省け、読む人にも読みやすい、良いコードになると思います。
ミス等あれば修正します。