1. はじめに
会社で第4回の勉強会を主催したので学習内容のメモとなります。
条件分岐の解きほぐし方に関する勉強会を開きました。
2. 概要
今回のテーマは、条件分岐の解きほぐし方です。
今回の記事は、RPGを例にストラテジーパターンについて説明しています。
※解釈がずれている箇所がありましたら教えていただければと思います。
参考資料[1]:「良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方」
3. 場面設定
3.1. 問題
呪文選択の処理を普通に書いてみる(if/elseで書いても大体おなじ)
この段階で考えられる問題点
- Magicクラスの関心事は呪文が増えるたびに増加していく
- データやロジックの関係性が複雑化 nameを共有しているとか
enum MagicType {
fire,
burizado,
sanda
}
// Magic magic = new Magic(MagicType.fire);
public class Magic {
final String name;
Magic(MagicType magicType) {
switch(magicType) {
case fire:
this.name = "ファイア";
break;
case sanda:
this.name = "サンダー";
break;
case burizado:
this.name = "ブリザド";
break;
default:
this.name = "";
break;
}
}
}
では、勇者のほかにロビンフッドがいたとします。
ロビンフッドが使える魔法はブリザドだけだとした時に次のような問題があります。
- ロビンフッドにはファイアとサンダーの処理を意識させたくない(関心事を分けたい)
switch(magicType) {
/* ロビンフッドには不要な関心事
case fire:
this.name = "ファイア";
break;
case sanda:
this.name = "サンダー";
break;
*/
case burizado:
this.name = "ブリザド";
break;
default:
this.name = "";
break;
}
4 ストラテジーパターンで改善
それでは条件分岐をストラテジーパターンで解きほぐしていきます。
4.1 魔法の処理をクラスに分ける
public class Burizado {
private final Member member;
Burizado(Member member) {
this.member = member;
}
public String name() {
return "ブリザド";
}
public MagicPoint cost() {
return new MagicPoint(3);
}
}
4.2 インターフェースを作る
メリット
- 各魔法の処理がどこにあるかがわかりやすい
- Fireに変更を加えてもBurizadoには影響なし
- switchだとインスタンス変数の変更影響など考えないといけないかも
public interface Magic {
public String name();
public MagicPoint cost();
}
Magic fire = new Fire(member);
Magic brizado = new Burizado(member);
4.3 必要のない魔法の関心事をなくす
Mapを使って実装してみる。
Magic Interfaceで共通化してることにより、1役者1つのMapで魔法を管理することが可能。
Map<MagicType, Magic> yuusya = new HashMap<MagicType, Magic>();
Map<MagicType, Magic> robinhood = new HashMap<MagicType, Magic>();
Magic fire = new Fire(member);
Magic brizado = new Burizado(member);
yuusya.put(MagicType.fire, fire);
yuusya.put(MagicType.burizado, brizado);
robinhood.put(MagicType.burizado, brizado);
System.out.println(yuusya.get(MagicType.fire).name()); // ファイア
System.out.println(yuusya.get(MagicType.burizado).name()); // ブリザド
System.out.println(robinhood.get(MagicType.burizado).name()); // ブリザド
System.out.println(robinhood.get(MagicType.fire).name()); // エラー
メリット
- robinhoodの関心事を制限できている
- 魔法使用の拡張性もしやすい
robinhood.put(MagicType.burizado, brizado);
System.out.println(robinhood.get(MagicType.burizado).name()); // ブリザド
System.out.println(robinhood.get(MagicType.fire).name()); // エラー
// 魔法を追加したかったらMapに対してkey/valueを追加してあげる
robinhood.put(MagicType.fire, fire);
robinhood.put(MagicType.sanda, sanda);
System.out.println(robinhood.get(MagicType.fire).name()); // ファイアー
System.out.println(robinhood.get(MagicType.sanda).name()); // サンダー
5. まとめ
今回のゴール:条件分岐をスマートに書く方法を知る。
条件分岐の役割をクラスそのものに任せる。
インターフェースを使って関心事の分離を行う
→ 条件分岐を解きほぐすこともできる
6. 参考文献
- 良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方