はじめに
こんにちは。
会社では「鍋島」と呼ばれていますが、この名前もようやく慣れてきました。
前回はStrategyパターンの記事を投稿しました。
今回も同様に、次のデザインパターンについての説明をしていきたいと思います。
また買い物シリーズです
Builderパターン
今回はBuilderパターンの説明をします。
Builderパターンとは、複雑なインスタンスを作る際にそのクラスを生成するためのBuilderクラスを作ります。
たとえば、Aというクラスのインスタンスを作るとしましょう。
Aというクラスは非常に多くのパラメータがあるとします。
これを毎回すべてコンストラクタに渡すと、このような問題が起こることがあります。
- パラメータが多すぎてソースコードの見通しが悪くなってしまう
- 必須のパラメータが一部しかないときも、コンストラクタですべて設定をする必要が出てきてしまう
そういった時にBuilderパターンを利用します。
Builderパターンを利用した場合、その問題となっている処理をBuilderクラスで行うようすることで、そのクラスを使う側は不要な処理を書く必要がなくなります。
このように、コードの見通しが良くなり、インスタンス作成が楽になります。
サンプルコード
例えば買い物に行った際に、レジで会計をしてレシートができるフローを考えてみましょう。
レジでは商品をバーコードリーダーで、一つずつ読み取っていきます。
そして読み取った商品をレシートに記録をし、すべての会計が完了するとレシートを出力します。
この場合、「レシート」インスタンスを構築するためのパラメータ(商品)の数が不定とななります。
このケースは、よりBuilderパターンが有効に作用すると考えられます。
更に今回は、会計中の一部で商品の購入をキャンセルするパターンも考慮します。
この場合、Builderに対して商品の削除を行います。
では、早速実装してみましょう。
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パターンを解説していきます。