LoginSignup
3
1

More than 3 years have passed since last update.

Scalaでデザインパターン ~Builderパターン~

Last updated at Posted at 2020-02-03

はじめに

こんにちは。
会社では「鍋島」と呼ばれていますが、この名前もようやく慣れてきました。

前回はStrategyパターンの記事を投稿しました。
今回も同様に、次のデザインパターンについての説明をしていきたいと思います。
また買い物シリーズです

Builderパターン

今回はBuilderパターンの説明をします。
Builderパターンとは、複雑なインスタンスを作る際にそのクラスを生成するためのBuilderクラスを作ります。

たとえば、Aというクラスのインスタンスを作るとしましょう。
Aというクラスは非常に多くのパラメータがあるとします。
これを毎回すべてコンストラクタに渡すと、このような問題が起こることがあります。

  • パラメータが多すぎてソースコードの見通しが悪くなってしまう
  • 必須のパラメータが一部しかないときも、コンストラクタですべて設定をする必要が出てきてしまう

そういった時にBuilderパターンを利用します。

Builderパターンを利用した場合、その問題となっている処理をBuilderクラスで行うようすることで、そのクラスを使う側は不要な処理を書く必要がなくなります。

このように、コードの見通しが良くなり、インスタンス作成が楽になります。

サンプルコード

例えば買い物に行った際に、レジで会計をしてレシートができるフローを考えてみましょう。
レジでは商品をバーコードリーダーで、一つずつ読み取っていきます。
そして読み取った商品をレシートに記録をし、すべての会計が完了するとレシートを出力します。
この場合、「レシート」インスタンスを構築するためのパラメータ(商品)の数が不定とななります。
このケースは、よりBuilderパターンが有効に作用すると考えられます。

更に今回は、会計中の一部で商品の購入をキャンセルするパターンも考慮します。
この場合、Builderに対して商品の削除を行います。
では、早速実装してみましょう。

Screenshot from 2020-01-21 12-02-25.png

import scala.math._

object Main extends App {
  val products = new ProductRepository().create(10)
  val user = User("nabeshi", 10000, products)
  val register = new Register(new ReceiptBuilder())
  val receipt = register
    .addProducts(user.shoppingBasket)
    .cancelProduct(user.shoppingBasket(1))
    .calculate(user.money)
  receipt.print()
}

// ReceiptBuilderを操作するクラス。ユーザーが商品を買う時はこのクラスを使用する
class Register(builder: ReceiptBuilder) {
  // 商品をbuilderに追加する
  def addProducts(products: Seq[Product]): Register= {
    new Register(products.foldLeft(builder)((builder, product) => builder.addProduct(product)))
  }
  // 商品をキャンセルする
  def cancelProduct(product: Product): Register = {
    new Register(builder.cancelProduct(product))
  }
  // 計算をしてレシートを出力する
  def calculate(money: Int): Receipt = builder.build(money)
}

// 商品を取り出すためのクラス
class ProductRepository() {
  // 商品を取り出す
  def create(num: Int): List[Product] = (1 to num).toList.map(i => Product(s"product$i", floor(random() * 1000).toInt))
}

// 商品
case class Product(name: String, price: Int)

// 購入した商品をまとめるクラス
case class Receipt(products: Seq[Product], payment: Int, price: Int) {
  // レシートを表示する
  def print(): Unit = {
    products.foreach(product => println(s"${product.name}: ${product.price} 円"))
    println(s"合計: $price 円")
    println(s"お預かり: $payment 円")
    println(s"お釣り: ${payment - price} 円")
  }
}

// builderクラス。商品を追加していきbuild関数が呼び出されたタイミングで、レシートを生成している
class ReceiptBuilder(private val productList: Seq[Product] = Seq()) {
  // レジを通した商品を追加していく
  def addProduct(product: Product): ReceiptBuilder = new ReceiptBuilder(productList :+ product)
  // 商品をキャンセルする
  def cancelProduct(product: Product): ReceiptBuilder = new ReceiptBuilder(productList.diff(Seq(product)))
  // レシートを生成する
  def build(money: Int): Receipt = Receipt(productList, money, productList.map(_.price).sum)
}

// 商品を買うユーザー
case class User(name: String, money: Int, shoppingBasket: Seq[Product])

解説

それでは、サンプルコードの解説をしていきます。
まずは、それぞれのクラス定義について解説します。

  • Register ここに購入する商品を入れることでレシートを生成します。
  • ProductRepository ここから商品を取り出します。
  • Product 商品です。
  • Receipt レシートオブジェクトです。このインスタンスを生成します。
  • ReceiptBuilder 今回のBuilderです。ここでレシートを生成します。

次にBuilderパターンをどのように利用しているかを解説します。
上でも書きましたが、ReceiptBuilderが今回のBuilderパターンの肝です。
ReceipBuilderによってReceiptを作ります。
このReceipBuilderに、バーコードリーダーで読み取った商品を、addProduct関数で追加していきます。
すべての商品を追加し終わったら、accouting関数を呼び出すことでレシートを生成しています。

このようにして、ReceipBuilderクラスは、レシート生成するために必要な処理を集約します。

最後に

最後まで読んでくださりありがとうございます。
Builderパターンのメリットが伝わりましたでしょうか?
よかったら利用してみてください

以上です。

次回は、FactoryMethodパターンを解説していきます。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1