ababup1192
3/31/2015 - 2:41 PM

FP in Scala. p4~11 Part1 関数型プログラミングの基礎 タグ管理

FP in Scala. p4~11 Part1 関数型プログラミングの基礎 タグ管理

package org.ababup1192

case class Charge(creditCard: CreditCard, amount: Int) {
  def combine(other: Charge): Charge = {
    if (creditCard == other.creditCard) {
      Charge(creditCard, amount + other.amount)
    } else {
      throw new Exception("Can't combine charges to different card")
    }
  }
}

trait Payments {
  def charge(creditCard: CreditCard, price: Int): Unit
}

// 実際の決済を行わないモックを挟むことで、テストがしやすくなる。
object PaymentsMock extends Payments {
  override def charge(creditCard: CreditCard, price: Int): Unit = {
    println(s"CreditCard charge $price.")
  }
}

class CreditCard {
  override def toString: String = {
    s"CreditCard"
  }
}
package org.ababup1192

object CafeSimulator extends App {

  val cafe = new Cafe

  val creditCard = new CreditCard()

  println("---- Single payment test. ----")
  println(cafe.buyCoffee(creditCard))

  println("---- Multiple payment test. ----")
  println(cafe.buyCoffees(creditCard, 10))
  // Payments traitを実装したオブジェクトは引き続き使える。
  val (coffees, charge) = cafe.buyCoffees(creditCard, 10)
  PaymentsMock.charge(charge.creditCard, charge.amount)
}
package org.ababup1192

class Coffee(val price: Int) {
  // 補助コンストラクタ ↑基本コンストラクタを必ず呼び出す。
  def this() = this(300)

  override def toString: String = {
    s"Coffee($price)"
  }
}

class Cafe {

  def buyCoffee(creditCard: CreditCard): (Coffee, Charge) = {
    val cup = new Coffee()
    // 決済処理はここでは行わず、コーヒーとChargeオブジェクトを返すだけ。副作用が無くなり、テストが非常に簡単に。
    (cup, Charge(creditCard, cup.price))
  }

  // 決済をその場で行わない(副作用がない)buyCoffee関数の利用とChargeのcombine関数により一括決済が可能に。
  def buyCoffees(creditCard: CreditCard, n: Int): (List[Coffee], Charge) = {
    val purchases: List[(Coffee, Charge)] = List.fill(n)(buyCoffee(creditCard))
    // List((1,"a"),(2,"b"),(3,"c"),(4,"d")).unzip
    // res: (List[Int], List[String]) = (List(1, 2, 3, 4),List(a, b, c, d))
    val (coffees, charges) = purchases.unzip
    // Chargeのリストから1つずつ要素を取り出して行き、まとめて1つのChargeへ
    (coffees, charges.reduce((c1, c2) => c1.combine(c2)))
  }

  // 無造作なChargeリストからクレジットカードごとにまとめChargeをまとめる。そうすることで手数料を節約できる。
  // List(Charge(AliceのCard, 100), Charge(BobのCard, 100), Charge(AliceのCard, 200)) =>
  //                                            List(Charge(AliceのCard, 300), Charge(BobのCard, 100))
  def coalesce(charges: List[Charge]): List[Charge] = {
    charges.groupBy(_.creditCard).values.map(_.reduce(_ combine _)).toList
  }

}