こんにちは。KOUKIです。
以前、Bilderパターンについて記事を書きましたが、別パターンでも実装したので紹介します。
Builderパターン
Builderパターンは、オブジェクトを生成する過程を抽象化することで、動的なオブジェクトの生成を可能にするパターンです。
プログラムの規模が大きくなると扱うオブジェクトもまた巨大化する傾向があります。また、似た機能をもった別のプロセスで作られたオブジェクトが大量に発生するかもしれません。
例えば、乗り物を例に考えてみましょう。
車、バイクの2種類のオブジェクトを作りたいとします。それぞれのパラメータは、以下です。
<車>
名前 — Car
席 — 5
車輪 — 4
<バイク>
名前 — MotorBike
席 — 1
車輪 — 2
こうしてみると必要なパーツは少し異なります。しかし、オブジェクトを生成するプロセスは抽象化できます。
<生成プロセス>
1. 車輪をセットする
2. 席をセットする
3. 組み立てをする
4. オブジェクトを取得する
この様に生成プロセス(Builder)を抽象化させることで、一定のルールを設けることができます。
この事を踏まえて、プログラミングしてみましょう。ちなみに、Go言語にはオブジェクトという概念はないのですが、ここでは便宜上、インスタンスをオブジェクトと呼ぶことにします。
生成プロセスをインターフェースで定義する
一番重要な生成プロセスをインターフェースで定義しましょう。
1 2 3 4 5 6 7 8 |
package builder type BuildProcess interface { SetWheels() BuildProcess SetSeats() BuildProcess SetStructure() BuildProcess GetVehicle() VehicleProduct } |
Directorを定義
オブジェクトを組み立てるために、Director(監督者)を用意します。
1 2 3 4 5 6 7 8 9 10 11 12 |
// Director type ManufacturingDirector struct { builder BuildProcess } func (f *ManufacturingDirector) Construct() { f.builder.SetSeats().SetStructure().SetWheels() } func (f *ManufacturingDirector) SetBuilder(b BuildProcess) { f.builder = b } |
具体的な使い方はテストコードを見ていただけるとわかると思います。
プロダクト情報
乗り物の情報をStructで定義しましょう。
1 2 3 4 5 6 |
// Product type VehicleProduct struct { Wheels int Seats int Structure string } |
Carの生成
準備は整ったので、Carを作成するメソッドを定義しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// A Builder of type car type CarBuilder struct { v VehicleProduct } func (c *CarBuilder) SetWheels() BuildProcess { c.v.Wheels = 4 return c } func (c *CarBuilder) SetSeats() BuildProcess { c.v.Seats = 5 return c } func (c *CarBuilder) SetStructure() BuildProcess { c.v.Structure = "Car" return c } func (c *CarBuilder) GetVehicle() VehicleProduct { return c.v } |
Carには、BuildProcessインターフェースで定義した関数をメソッドとして全て定義しています。これによりインターフェースを実装したことになるので、DirectorのSetBuilderメソッドに引数として渡すことができる様になります。
Motorbikeの生成
Carと同じようにMotorbikeを作成するメソッドを実装しましょう。もしかしたら、もうピンとくるかもしれませんね^^
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// A Builder of type Motorbike type BikeBuilder struct { v VehicleProduct } func (b *BikeBuilder) SetWheels() BuildProcess { b.v.Wheels = 2 return b } func (b *BikeBuilder) SetSeats() BuildProcess { b.v.Seats = 1 return b } func (b *BikeBuilder) SetStructure() BuildProcess { b.v.Structure = "MotorBike" return b } func (b *BikeBuilder) GetVehicle() VehicleProduct { return b.v } |
Carと同じメソッドを定義しました。これにより、MotorBikeもDirectorに渡せます。
これにより、オブジェクトの生成プロセスをCarと同じにできました。
「乗り物を実装したい時は、定義した生成プロセスに沿って実装する」ということをルール化できたわけですね。
テストコードの実装
テストコードで、Builderパターンの使い方を確認しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
package builder import "testing" func TestBuilderPattern(t *testing.T) { // Director manufacturingComplex := ManufacturingDirector{} // Car carBuilder := &CarBuilder{} // DirectorにCarを渡す manufacturingComplex.SetBuilder(carBuilder) // 組み立て manufacturingComplex.Construct() // Carの情報を取得 car := carBuilder.GetVehicle() if car.Wheels != 4 { t.Errorf( "Wheels on a car must be 4 and they were %d\n", car.Wheels) } if car.Structure != "Car" { t.Errorf( "Structure on a car must be 'Car' and was %s\n", car.Structure) } if car.Seats != 5 { t.Errorf("Seats on a car must be 5 and they were %d\n", car.Seats) } // Bike bikeBuilder := BikeBuilder{} manufacturingComplex.SetBuilder(&bikeBuilder) manufacturingComplex.Construct() motorbike := bikeBuilder.GetVehicle() if motorbike.Wheels != 2 { t.Errorf( "Wheels on a car must be 2 and they were %d\n", motorbike.Wheels) } if motorbike.Structure != "MotorBike" { t.Errorf( "Structure on a motorbike must be 'MotorBike' and was %s\n", motorbike.Structure) } if motorbike.Seats != 1 { t.Errorf("Seats on a motorbike must be 1 and they were %d\n", motorbike.Seats) } } |
上記のコードでは、DirectorにCar及びMortarbikeのオブジェクトを渡し、組み立てを行っています。
そして、GetVechicleメソッドにて、生成済みのインスタンスを取得しています。
このように、Builderパターンはオブジェクトの生成に一定のルールを設けられる結構便利なパターンなのです。
最後にテストコードを実行しましょう。
1 2 3 4 5 |
$ go test -v -run=TestBuilder === RUN TestBuilderPattern --- PASS: TestBuilderPattern (0.00s) PASS ok github.com/hoge/go-algorithms/design-pattern/builder 0.376s |
OKですね。
おわりに
Builderパターンは、コマンドラインツールを作成するときに使う機会があるかもしれませんね。コマンドラインツールは特定のルールに従って何らかの成果物を生成したいときに使いますので。
例えば、OS(Linux, Mac, Windows)別のログ出力ツールとかならイメージが沸きやすいかもですね。
やりたいこと(ログの切り抜きなど)は同じですが、オブジェクト(ファイルシステムなど)は異なると思うので、その辺を抽象化するといい感じのツールができそうです。
いつか機会があればやってみようと思います^^
それでは、また!
Go記事まとめ
ソースコード
builder.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
package builder type BuildProcess interface { SetWheels() BuildProcess SetSeats() BuildProcess SetStructure() BuildProcess GetVehicle() VehicleProduct } // Director type ManufacturingDirector struct { builder BuildProcess } func (f *ManufacturingDirector) Construct() { f.builder.SetSeats().SetStructure().SetWheels() } func (f *ManufacturingDirector) SetBuilder(b BuildProcess) { f.builder = b } // Product type VehicleProduct struct { Wheels int Seats int Structure string } // A Builder of type car type CarBuilder struct { v VehicleProduct } func (c *CarBuilder) SetWheels() BuildProcess { c.v.Wheels = 4 return c } func (c *CarBuilder) SetSeats() BuildProcess { c.v.Seats = 5 return c } func (c *CarBuilder) SetStructure() BuildProcess { c.v.Structure = "Car" return c } func (c *CarBuilder) GetVehicle() VehicleProduct { return c.v } // A Builder of type Motorbike type BikeBuilder struct { v VehicleProduct } func (b *BikeBuilder) SetWheels() BuildProcess { b.v.Wheels = 2 return b } func (b *BikeBuilder) SetSeats() BuildProcess { b.v.Seats = 1 return b } func (b *BikeBuilder) SetStructure() BuildProcess { b.v.Structure = "MotorBike" return b } func (b *BikeBuilder) GetVehicle() VehicleProduct { return b.v } |
builder_test.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
package builder import "testing" func TestBuilderPattern(t *testing.T) { // Director manufacturingComplex := ManufacturingDirector{} // Car carBuilder := &CarBuilder{} // DirectorにCarを渡す manufacturingComplex.SetBuilder(carBuilder) // 組み立て manufacturingComplex.Construct() // Carの情報を取得 car := carBuilder.GetVehicle() if car.Wheels != 4 { t.Errorf( "Wheels on a car must be 4 and they were %d\n", car.Wheels) } if car.Structure != "Car" { t.Errorf( "Structure on a car must be 'Car' and was %s\n", car.Structure) } if car.Seats != 5 { t.Errorf("Seats on a car must be 5 and they were %d\n", car.Seats) } // Bike bikeBuilder := BikeBuilder{} manufacturingComplex.SetBuilder(&bikeBuilder) manufacturingComplex.Construct() motorbike := bikeBuilder.GetVehicle() if motorbike.Wheels != 2 { t.Errorf( "Wheels on a car must be 2 and they were %d\n", motorbike.Wheels) } if motorbike.Structure != "MotorBike" { t.Errorf( "Structure on a motorbike must be 'MotorBike' and was %s\n", motorbike.Structure) } if motorbike.Seats != 1 { t.Errorf("Seats on a motorbike must be 1 and they were %d\n", motorbike.Seats) } } |
コメントを残す
コメントを投稿するにはログインしてください。