#Builder
目次
Abstract Factoryがその名の通り工場であり各部品を製造する機能を提供するならば、本パターンは部品を組み合わせた製品(複合オブジェクト)を量産するようなイメージながかな?
##目的
複合オブジェクトについて、その作成過程を表現形式に依存しないものにすることにより、同じ作成過程で異なる表現形式のオブジェクトを生成できるようにする。
##構成要素
・Builder Productクラスを生成するための抽象クラス
・ConcreteBuilder 具象Builderクラス。Productクラスを生成する
・Director Builderクラスのインタフェースを使ってオブジェクトを生成する
・Product 多くの構成要素からなる複合オブジェクト
##実装
下記のようなフローでクリエイターが街づくりを行います。
専門の建築業者へ建てたい建築物を依頼 -> 建物のフロアの数、部屋の数を決める -> 希望する数だけ建ててもらう
###Builder Productクラスを生成するための抽象クラス
建築業者インターフェース
package builder
interface Builder {
enum class ProductType(val value: String) {
ArtMuseum("美術館"),
Museum("博物館"),
MovieTheater("映画館")
}
fun getProduct(): Product
fun addFloor(floorNum: Int)
fun addRoom(targetFloor: Int, roomNo: Int)
}
###ConcreteBuilder 具象Builderクラス。Productクラスを生成する
博物館専門建築業者具象クラス
package builder
class MuseumBuilder(productName: String): Builder {
private var product = Product(Builder.ProductType.Museum, productName)
override fun getProduct(): Product {
product.countUpProductNumber()
return product.clone() as Product
}
override fun addFloor(floorNum: Int) {
if (product.floorList.asSequence().filter { floor -> floor.floorNum == floorNum }.count() == 0) {
product.floorList.add(Floor(floorNum))
}
}
override fun addRoom(targetFloor: Int, roomNo: Int) {
val floor= product.floorList.filter { floor -> floor.floorNum== targetFloor }
if (floor.count() > 0) {
floor[0].addRoom(roomNo)
}
}
}
美術館専門建築業者具象クラス
package builder
class ArtMuseumBuilder(productName: String) : Builder {
private var product = Product(Builder.ProductType.ArtMuseum, productName)
override fun getProduct(): Product {
product.countUpProductNumber()
return product.clone() as Product
}
override fun addFloor(floorNum: Int) {
if (product.floorList.asSequence().filter { floor -> floor.floorNum == floorNum }.count() == 0) {
product.floorList.add(Floor(floorNum))
}
}
override fun addRoom(targetFloor: Int, roomNo: Int) {
val floor= product.floorList.filter { floor -> floor.floorNum== targetFloor }
if (floor.count() > 0) {
floor[0].addRoom(roomNo)
}
}
}
映画館専門建築業者具象クラス
package builder
class MovieTheaterBuilder(productName: String): Builder {
private var product = Product(Builder.ProductType.MovieTheater, productName)
override fun getProduct(): Product {
product.countUpProductNumber()
return product.clone() as Product
}
override fun addFloor(floorNum: Int) {
if (product.floorList.asSequence().filter { floor -> floor.floorNum == floorNum }.count() == 0) {
product.floorList.add(Floor(floorNum))
}
}
override fun addRoom(targetFloor: Int, roomNo: Int) {
val floor= product.floorList.filter { floor -> floor.floorNum== targetFloor }
if (floor.count() > 0) {
floor[0].addRoom(roomNo)
}
}
}
建物~部屋までは読み飛ばしてもらって結構です。
###Product 多くの構成要素からなる複合オブジェクト
建物クラス
package builder
class Product(productType: Builder.ProductType, productName: String): Cloneable {
var productType = productType
val floorList: MutableList<Floor> = mutableListOf()
var productName = productName
var productNumber = 0
fun countUpProductNumber() {
productNumber++
}
fun show(): String {
var ret = "【建物】${productType.value}:$productName$productNumber 棟目"
for (floor in floorList) {
ret += floor.show()
}
return ret
}
public override fun clone(): Any {
return super.clone()
}
}
階層クラス 1階 2階...
package builder
class Floor(floorNum: Int) {
var floorNum = floorNum
private val roomList:MutableList<Room> = mutableListOf()
fun addRoom(roomNo: Int) {
if (roomList.asSequence().filter { room -> room.roomNo == roomNo }.count() == 0) {
roomList.add(Room(roomNo))
}
}
fun show(): String {
var ret: String = "【階層】$floorNum 階 "
for (room in roomList) {
ret += room.show()
}
return ret
}
}
部屋クラス
package builder
class Room(roomNo: Int) {
val roomNo = roomNo
fun show(): String {
return "【部屋】 $roomNo 号室"
}
}
###Director Builderクラスのインタフェースを使ってオブジェクトを生成する
街を作る人クラス
package builder
class Creator {
init {
var productList:MutableList<Product> = mutableListOf()
// 西洋美術館の建築を美術館専門建築業者に依頼する
var artMuseumBuilder1 = ArtMuseumBuilder("西洋美術館").apply {
addFloor(1)
addFloor(2)
addFloor(3)
addRoom(1, 101)
addRoom(1, 102)
addRoom(2, 201)
addRoom(3, 301)
}
// 東洋美術館の建築を美術館専門建築業者に依頼する
var artMuseumBuilder2 = ArtMuseumBuilder("東洋美術館").apply {
addFloor(1)
addFloor(2)
addRoom(1, 101)
addRoom(2, 201)
}
// 国立博物館の建築を博物館専門建築業者に依頼する
var museumBuilder = MuseumBuilder("国立博物館").apply {
addFloor(1)
addRoom(1, 101)
}
// ホーゲーシネマズの建築を映画館専門建築業者に依頼する
var movieTheaterBuilder = MovieTheaterBuilder("HOGEシネマズ").apply {
addFloor(1)
addRoom(1, 101)
}
// 街に建築するリスト作成
productList.add(artMuseumBuilder1.getProduct())
productList.add(artMuseumBuilder1.getProduct())
productList.add(artMuseumBuilder1.getProduct())
productList.add(artMuseumBuilder2.getProduct())
productList.add(artMuseumBuilder2.getProduct())
productList.add(museumBuilder.getProduct())
productList.add(movieTheaterBuilder.getProduct())
for (product in productList) {
println(product.show())
}
}
}
クリエイターはどんな建物を街に建てたいかを考え、それぞれの建築業者へ依頼し、必要な分だけ建ててもらいます。
コンビニとかにしたほうがわかりやすかったろうか...
あんまり作りこんでもしょうがないので簡潔な構成にしていますが、住所プロパティなんかをProductクラスに保持させ、建ててもらうgetProduct()
たびに住所プロパティを設定できるようにすればよりイメージしやすかったか?
以上で製品(複合オブジェクト)を量産するという目的は達成できました。
街作りの途中で映画館をもう一軒追加したい!場合もmovieTheaterBuilder.getProduct()
を呼べば同じ建物を**複製(量産)**することができます。各Builder#getProduct()メソッドのreturn product.clone() as Product
がいい仕事してますね。
###出力結果
[output]
【建物】美術館:西洋美術館1 棟目【階層】1 階 【部屋】 101 号室【部屋】 102 号室【階層】2 階 【部屋】 201 号室【階層】3 階 【部屋】 301 号室
【建物】美術館:西洋美術館2 棟目【階層】1 階 【部屋】 101 号室【部屋】 102 号室【階層】2 階 【部屋】 201 号室【階層】3 階 【部屋】 301 号室
【建物】美術館:西洋美術館3 棟目【階層】1 階 【部屋】 101 号室【部屋】 102 号室【階層】2 階 【部屋】 201 号室【階層】3 階 【部屋】 301 号室
【建物】美術館:東洋美術館1 棟目【階層】1 階 【部屋】 101 号室【階層】2 階 【部屋】 201 号室
【建物】美術館:東洋美術館2 棟目【階層】1 階 【部屋】 101 号室【階層】2 階 【部屋】 201 号室
【建物】博物館:国立博物館1 棟目【階層】1 階 【部屋】 101 号室
【建物】映画館:HOGEシネマズ1 棟目【階層】1 階 【部屋】 101 号室
もし、product.clone()
していなければ下記のような結果になり、何の役にも立たない建築業者ができあがります。
[output]
【建物】美術館:西洋美術館3 棟目 ...
【建物】美術館:西洋美術館3 棟目 ...
【建物】美術館:西洋美術館3 棟目 ...
【建物】美術館:東洋美術館2 棟目 ...
【建物】美術館:東洋美術館2 棟目 ...
【建物】博物館:国立博物館1 棟目 ...
【建物】映画館:HOGEシネマズ1 ...