Background
在追求簡潔的路上, 大量複雜的判斷可能會採用 switch 的方式判斷, 而為了限制狀態, 可能會搭配 enums 配合, 讓 switch conditions 更容易理解與維護.
enum Player {
// 戰士
WARRIOR,
// 弓箭手
ARCHER,
// 法師
WIZARD,
}
String swordAttack() {
return "劍術攻擊";
}
String shootAttack() {
return "射箭攻擊";
}
String fireAttack() {
return "火焰攻擊";
}
// 攻擊
String attack(Player player) {
switch(player) {
// 戰士攻擊
case WARRIOR:
return this.swordAttack();
// 弓箭手攻擊
case ARCHER:
return this.shootAttack();
// 法師攻擊
case WIZARD:
return this.fireAttack();
// 其他職業拋出異常
default:
throw new IllegalArgumentException("Invalid player: " + player);
}
}
像這樣的 code, 其實有更優雅的 strategy pattern 可以套用, 但在效益與成本的取捨上又有點用牛刀殺雞的感覺.
用 supplier 重構
Supplier
是 java8+ 的 lambda 滿好用的特性之一, 顧名思義就是一種供應手段, 用來封裝無參數的方法, 使其成為一種 Supplier, 封裝到 Supplier 的 method, 並不會馬上被執行, 而是透過 get()
, 在某些層面上也算是一種 lazy loading 的應用.
String attack(Player player) {
switch(player) {
// 戰士攻擊
case WARRIOR:
Supplier<String> warriorSupplier = () -> this.swordAttack();
return warriorSupplier.get();
// 弓箭手攻擊
case ARCHER:
Supplier<String> archerSupplier = () -> this.shootAttack();
return archerSupplier.get();
// 法師攻擊
case WIZARD:
Supplier<String> wizardSupplier = () -> this.fireAttack();
return wizardSupplier.get();
// 其他職業拋出異常
default:
throw new IllegalArgumentException("Invalid player: " + player);
}
}
用 Map 重構
因為 Enums 讓狀態變成有限性, 所以 Map 的資料結構是一個更明確的作法, key, value 就直接對應成 Enums, Supplier.
String attack(Player player) {
Map<Player, Supplier<String>> attackMap = new HashMap();
attackMap.put(Player.WARRIOR, () -> this.swordAttack());
attackMap.put(Player.ARCHER, () -> this.shootAttack());
attackMap.put(Player.WIZARD, () -> this.fireAttack());
用 Stream 重構
用 stream 在開發體驗上可以以 stream 流的方式做更多邏輯擴充與處理與優化, 比如 filter()
, sort()
之類的.
String attack(Player player) {
Map<Player, Supplier<String>> attackMap = new HashMap();
attackMap.put(Player.WARRIOR, () -> this.swordAttack());
attackMap.put(Player.ARCHER, () -> this.shootAttack());
attackMap.put(Player.WIZARD, () -> this.fireAttack());
// 確保線程安全, 推薦使用 Immutable 類型
final Map<Player, Supplier<Void>> map = Collections.unmodifiableMap(attackMap);
// 藉由 Stream 方式串接整個流程
map.entrySet().stream()
.filter(entry -> player.equals(entry.getKey()))
.map(Entry::getValue)
.map(Supplier::get)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Invalid type: " + type));
}
當然上述的案例很明確的就是依據 player 的狀態來執行 attack, 直接 get Map 處理就好了.
String attack(Player player) {
Map<Player, Supplier<String>> attackMap = new HashMap();
attackMap.put(Player.WARRIOR, () -> this.swordAttack());
attackMap.put(Player.ARCHER, () -> this.shootAttack());
attackMap.put(Player.WIZARD, () -> this.fireAttack());
// 確保線程安全, 推薦使用 Immutable 類型
final Map<Player, Supplier<String>> map = Collections.unmodifiableMap(attackMap);
return Optional.ofNullable(map.get(player))
.orElseThrow(() -> new IllegalArgumentException("Invalid type: " + player));
}