https://cinemacity.co.jp/ticket/
お題としては、この料金表に基づいて映画料金を決定するドメインモデルを作る。
- エムアイカード、駐車場パーク80割引
- 同伴者
- ※3D作品は一律プラス400円。3Dメガネ(Real D)持参の場合は、100円引き。
- ※【極上爆音上映】はレイトショー割適用外です。
複雑な条件をそのままコード化すると、ビジネスルールを明示的な概念として捉えづらいため、仕様パターン(Specification)を採用した。 プラン仕様(PlanSpecification)を使うと、映画のプランを決定するための条件を宣言的に記述できるようになる。PlanSpecificationには顧客仕様CustomerSpecification)と営業日(レイト)仕様(BusinessDaySpecification, LateSpec)などの仕様を内包して、複雑な概念をシンプルに扱えるようにする。
object Plans {
val lateSpec = LateSpecification(20)
object WeekdayNotLatePlans {
val cinemaCitizenPlan = Plan(
PlanName.CinemaCitizen,
Price(1000),
PlanSpecification(
customerSpecs = CustomerSpecification.CinemaCitizen,
businessDayWithLateSpec = Some(BusinessDaySpecification.Weekday and !lateSpec),
movieDaySpec = None
)
)
// ...
}
// ...
}
高い金額を提示するのはビジネスの都合上よくないので、安い金額順にプランをソートした上で該当の条件に合うプランを検索する。
final case class Plans(breachEncapsulationOfValue: Seq[Plan]) {
def filterByLocalDateTime(localDateTime: LocalDateTime): Plans =
copy(breachEncapsulationOfValue.filter { plan =>
val spec = plan.planSpec
spec.businessDayWithLateSpec
.fold(false)(_.isSatisfiedBy(localDateTime)) || spec.movieDaySpec.fold(false)(
_.isSatisfiedBy(localDateTime.toLocalDate)
)
})
def lowestPriceOrder: Plans =
copy(breachEncapsulationOfValue.sortBy(_.price.amount))
def findByCustomer(customer: Customer, localDateTime: LocalDateTime): Option[Plan] =
breachEncapsulationOfValue.find(_.planSpec.isSatisfiedBy(PlanCondition(customer, localDateTime)))
}
final case class Order(customer: Customer, orderedAt: LocalDateTime) {
def plan: Option[Plan] = {
Plans.all.lowestPriceOrder
.filterByLocalDateTime(orderedAt)
.findByCustomer(customer, orderedAt)
}
}
- Elm
- Go
- Java
- Kotlin
- Lisp
- PHP
- Scala
- Swift
- TypeScript
- Rust
- モデル図