【DDD練習】「JR 新幹線 料金ルールを実装してみよう」にチャレンジ(その4)


※ 個人blogに投稿した記事(投稿日:2020/1/29)をQiitaに移行しました

関連記事

前置き

シリーズ化してきているDDD練習の第4回です。

練習課題

今回対応する要件

季節(season)による特急指定席料金の変動

季節の区分

  • 通常期(regular)
  • 閑散期(off-peak)
  • 繁忙期(peak)

の三種類

12月から1月の繁忙期と閑散期

  • 繁忙期: 12月25日〜1月10日(年末年始)
  • 閑散期: 1月16日〜30日

料金の変動

通常期の指定席特急券に対して、

  • 閑散期は -200円
  • 繁忙期には +200円

自由席特急券

季節によって変動しない。
通常期の指定席特急券より530円を引いた金額で年間固定。

実装

まずは変動費(-200円、+200円)を表現する値オブジェクトを実装します。

public class ReservedSeatSeasonVariable
    {
        public readonly int value;

        public ReservedSeatSeasonVariable(int value)
        {
            this.value = value;
        }
    }

季節(Season)をインターフェイスで実装します。

public interface Season
    {
        int variableAmount();
    }

続いて、Seasonインターフェイスを実装した季節ごとクラスを作成します。

  • 通常期(regular)
  • 閑散期(off-peak)
  • 繁忙期(peak)

季節の期間は毎年変動すると見越して、DBで定義された値から取得する形が良いかもしれません。
今回は取り急ぎ定数的に実装します。

public class PeakSeason : Season
    {
        public readonly ReservedSeatSeasonVariable reservedSeatSeasonVariable = 
            new ReservedSeatSeasonVariable(200);

        public int variableAmount()
        {
            return reservedSeatSeasonVariable.value;
        }

        public static bool during(BoardingDate boardingDate)
        {
            if (boardingDate.value.Month == 12 && boardingDate.value.Day >= 25) { return true; };
            return boardingDate.value.Month == 1 && boardingDate.value.Day <= 10;
        }

    }
public class OffPeakSeason : Season
    {
        public readonly ReservedSeatSeasonVariable reservedSeatSeasonVariable = 
            new ReservedSeatSeasonVariable(-200);

        public int variableAmount()
        {
            return reservedSeatSeasonVariable.value;
        }

        public static bool during(BoardingDate boardingDate)
        {
            if (boardingDate.value.Month == 1 && boardingDate.value.Day >= 16) { return true; };
            return boardingDate.value.Month == 1 && boardingDate.value.Day <= 30;
        }

    }
public class ReqularSeason : Season
    {
        public int variableAmount()
        {
            return 0;
        }
    }

そして、出発日(BoardingDate)から季節を判定するクラスSeasonTypeです。

public class SeasonType
    {
        public static Season valueOf(BoardingDate boardingDate)
        {
            switch (boardingDate)
            {
                case BoardingDate boarding when PeakSeason.during(boarding):
                    return new PeakSeason();
                case BoardingDate boarding when OffPeakSeason.during(boarding):
                    return new OffPeakSeason();
                default:
                    return new ReqularSeason();
            }
        }
    }

季節ごとの変動は指定席の特急料金だけに対してなので、実装済み(第1回)の指定席(ReservedSeat)クラスに渡せばよいだけです。

以上で実装完了です。
サービスクラスの差分は以下のようになりました。

振り返り

今回の練習課題は業務アプリケーションの開発の実戦に近く、非常に有意義なものであったと思います。
数人でモブプロやライブコーディングしながらやったら面白そうです。
ベテラン+初学者のグループでやったりすると、いろいろな気付きが出てきたりしそうです。

第2回まではクラスの表現に結構悩んだところもあったのですが、第3回、4回では慣れてきたのかスムーズにクラスの表現が出来たと思いました。
間を開けて実装してしまっているので、表現方法に若干統一感がないのと、クラスをもっと集約しても良いのかな、とも思います。
この辺りは今後リファクタリングにも取り組んでみたいです。
あと、リポジトリにはView側のプロジェクトも作っているのにそちらは力尽きてしまっているので・・・

今回の練習結果のリポジトリ