技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

javaでのBuilderパターン

Builderパターンは、処理内容を定義するBuilderクラスと、処理順番を定義するDirectorクラスの2つに分けることで、柔軟に処理を変更できるようにするパターンです。
Builderクラスの定義により処理内容が変わっても都度処理内容を記述する必要が無くなり、Directorクラスの定義により処理順番が変わっても都度処理順番を記述する必要が無くなります。
 
今回は、RPGのダメージ計算を模したサンプルコードを作成してみました。
ゲームが変わると処理内容が変わるのですが、Builderクラスを定義することで処理内容が変更されても利用者側で処理内容を都度記述する必要が無くなります。
また、ダメージを与える手段によってはダメージが可変になったり固定になったりするのですが、Directorクラスを定義することで利用者側で都度ダメージを可変にしたり固定にしたりするための具体的な記述をする必要が無くなります。
 
【サンプルコード】
・DamageBuilder.java
// ダメージ計算で使用する共通のメソッドを定義
public interface DamageBuilder {

    public void baseDamageCalc(int offenceValue,int defenceValue);
    public void damageRandomise();
    public Object getResult();

}
 
・DamageDto.java
public class DamageDto {

    private int DamageValue;

    public int getDamageValue() {
        return DamageValue;
    }

    public void setDamageValue(int damageValue) {
        DamageValue = damageValue;
    }

}
 
・DamageBuilderGameA.java
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
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
// ダメージがランダムで変化するケースで使用する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
// ダメージが固定のケースで使用する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
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);

    }
}
 
【実行結果】
■ゲームA用のダメージ計算(ダメージ可変ケース)
 ダメージ:40
■ゲームA用のダメージ計算(ダメージ固定ケース)
 ダメージ:50
■ゲームB用のダメージ計算(ダメージ可変ケース)
 ダメージ:9