Builderパターンは、処理内容を定義するBuilderクラスと、処理順番を定義するDirectorクラスの2つに分けることで、柔軟に処理を変更できるようにするパターンです。
Builderクラスの定義により処理内容が変わっても都度処理内容を記述する必要が無くなり、Directorクラスの定義により処理順番が変わっても都度処理順番を記述する必要が無くなります。
今回は、RPGのダメージ計算を模したサンプルコードを作成してみました。
ゲームが変わると処理内容が変わるのですが、Builderクラスを定義することで処理内容が変更されても利用者側で処理内容を都度記述する必要が無くなります。
また、ダメージを与える手段によってはダメージが可変になったり固定になったりするのですが、Directorクラスを定義することで利用者側で都度ダメージを可変にしたり固定にしたりするための具体的な記述をする必要が無くなります。
【サンプルコード】
・DamageBuilder.java
1 2 3 4 5 6 7 8 |
// ダメージ計算で使用する共通のメソッドを定義 public interface DamageBuilder { public void baseDamageCalc(int offenceValue,int defenceValue); public void damageRandomise(); public Object getResult(); } |
・DamageDto.java
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class DamageDto { private int DamageValue; public int getDamageValue() { return DamageValue; } public void setDamageValue(int damageValue) { DamageValue = damageValue; } } |
・DamageBuilderGameA.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import java.util.Random; // ゲームA用の計算式を記述 public class DamageBuilderGameA implements DamageBuilder { private DamageDto damageDto; public DamageBuilderGameA(){ this.damageDto = new DamageDto(); } public void baseDamageCalc(int offenceValue, int defenceValue) { int baseDamageValue = offenceValue - (defenceValue / 2); if (baseDamageValue < 0) { baseDamageValue = 0; } damageDto.setDamageValue(baseDamageValue); } public void damageRandomise() { // 4/5、5/5、6/5の何れかの値をかけてランダム化する damageDto.setDamageValue(damageDto.getDamageValue() * ((new Random()).nextInt(3) + 4) / 5); } public Object getResult() { return this.damageDto; } } |
・DamageBuilderGameB.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import java.util.Random; // ゲームB用の計算式を記述 public class DamageBuilderGameB implements DamageBuilder { private DamageDto damageDto; public DamageBuilderGameB(){ this.damageDto = new DamageDto(); } public void baseDamageCalc(int offenceValue, int defenceValue) { int baseDamageValue = offenceValue / (defenceValue / 10); if (baseDamageValue < 0) { baseDamageValue = 0; } damageDto.setDamageValue(baseDamageValue); } public void damageRandomise() { // 9/10、10/10、11/10の何れかの値をかけてランダム化する damageDto.setDamageValue(damageDto.getDamageValue() * ((new Random()).nextInt(3) + 9) / 10); } public Object getResult() { return this.damageDto; } } |
・DamageDirectorVariable.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// ダメージがランダムで変化するケースで使用するDirector public class DamageDirectorVariable { private DamageBuilder damageBuilder; public DamageDirectorVariable (DamageBuilder damageBuilder){ this.damageBuilder = damageBuilder; } public void constract(int offenceValue,int defenceValue){ damageBuilder.baseDamageCalc(offenceValue,defenceValue); damageBuilder.damageRandomise(); } } |
・DamageDirectorFixed.java
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// ダメージが固定のケースで使用するDirector public class DamageDirectorFixed { private DamageBuilder damageBuilder; public DamageDirectorFixed (DamageBuilder damageBuilder){ this.damageBuilder = damageBuilder; } public void constract(int offenceValue,int defenceValue){ damageBuilder.baseDamageCalc(offenceValue,defenceValue); } } |
・DamageCalcMain.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
public class DamageCalcMain { public static void main(String[] args){ int offenceValue = 100; int defenceValue = 100; int damageValue = 0; DamageBuilderGameA damageBuilderGameA = new DamageBuilderGameA(); DamageBuilderGameB damageBuilderGameB = new DamageBuilderGameB(); DamageDirectorVariable damageDirectorValiableGameA = new DamageDirectorVariable(damageBuilderGameA); DamageDirectorFixed damageDirectorFixedGameA = new DamageDirectorFixed(damageBuilderGameA); DamageDirectorVariable damageDirectorValiableGameB = new DamageDirectorVariable(damageBuilderGameB); System.out.println ("■ゲームA用のダメージ計算(ダメージ可変ケース)"); damageDirectorValiableGameA.constract(offenceValue, defenceValue); damageValue = ((DamageDto) damageBuilderGameA.getResult()).getDamageValue(); System.out.println(" ダメージ:" + damageValue); // Directorを定義することで利用者側で処理の順番を都度定義しなくて良い System.out.println ("■ゲームA用のダメージ計算(ダメージ固定ケース)"); damageDirectorFixedGameA.constract(offenceValue, defenceValue); damageValue = ((DamageDto) damageBuilderGameA.getResult()).getDamageValue(); System.out.println(" ダメージ:" + damageValue); // Builderを定義することで利用者側で処理の内容を都度定義しなくて良い System.out.println ("■ゲームB用のダメージ計算(ダメージ可変ケース)"); damageDirectorValiableGameB.constract(offenceValue, defenceValue); damageValue = ((DamageDto) damageBuilderGameB.getResult()).getDamageValue(); System.out.println(" ダメージ:" + damageValue); } } |
【実行結果】
1 2 3 4 5 6 |
■ゲームA用のダメージ計算(ダメージ可変ケース) ダメージ:40 ■ゲームA用のダメージ計算(ダメージ固定ケース) ダメージ:50 ■ゲームB用のダメージ計算(ダメージ可変ケース) ダメージ:9 |
いかがでしたでしょうか。
プログラムを作っていると、「処理の順番は同じなのに処理の中身が少しずつ違う…」といったことや「処理の中身は同じなのに処理の順番が少しずつ違う…」といったことがあると思います。私も、そのようなケースでは、共通関数は作りつつも同じ記述を何か所にもコピーせざるを得なくなり、かなり冗長なコードを作ってしまっていました。
Builderパターンは、そのようなケースで効果を発揮します。同じ記述を何か所にもコピーすると保守する時に(テストも含めて)大変なので、中身や順番が少しずつ違うなと思ったら適用することをお勧めします。
次回も、開発の役に立つ情報を発信していきたいと思います!
コメント