Goでデザインパターン (Builderパターン)

Goデザインパターン

Builderパターンとは

Builderパターンは、複雑なオブジェクトを段階的に構築できる、生成に関するデザインパターン。

例えばHouse(家)オブジェクトを作成する場合を考える。家といっても屋根の形や庭、ベランダ等の様々なオプションが考えられる。このようなオブジェクトが必要な場合、主な解決策は2つある。一つがHouseクラスを基底クラスにして、それをもとに様々なサブクラスを用意すること。もう一つが屋根の形やベランダの有無等のパラメータをすべてコンストラクタで渡すようにすることである。
全てのパターンのサブクラスを用意するのは非常に面倒であるし、コンストラクタで渡す場合も、あるオブジェクト生成する場合に、すべてのパラメータが必要になるわけではない。
これをオブジェクトの構築コードをBuilderに切り出すことで解決したのがBuilderパターンになる。Builderパターンではコンストラクタ内で行われる処理を、buildStepA,B,Cのように各ステップごとに実装し、生成の際にそれらを呼び出している。

Builderパターンを利用することで下記のようなメリットを得ることができる。

  • 異なる型・表現のオブジェクトを同じ構築コードを利用して生成することができる。
  • 複雑なオブジェクト構築手順をビジネスロジック(Client側処理)から隠蔽できる。
    (単一責任の原則)

クラス図

  1. iBuilder
    インスタンス生成に必要な様々な構築ステップのインターフェースを宣言する。
  2. concreteBuilder
    iBuilderで宣言された各ステップの処理を実装する。また生成したインスタンスを取得するためのメソッドが用意され
  3. Product
    生成されるインスタンスを定義する。
  4. Director
    iBuilderで宣言されたAPIを利用してインスタンスを生成する。オブジェクト生成の一連のステップ呼び出しを抽出している。よく使われる生成手順を置いておいて再利用しやすくしている。
    concreteBuilderに依存した処理は行わない。→Builderインターフェースを実装してさえいれば、別の型のオブジェクトでも生成可能。
    厳密には必要ないが、Clientからインスタンス構築の詳細を完全に隠蔽することができる。
  5. Client
    Builderオブジェクトの一つをDirectorオブジェクトに紐づける。Directorに存在しない生成方法の場合はBuilderの構築ステップを直接実行する。

実装

iBuilder

package builder

// インスタンスを作成するためのインターフェース(API)を宣言する
// インスタンスの各ステップのメソッドを用意する。
type iBuilder interface {
	SetHandle()
	SetTireSize()
	SetFrame()
	Build() interface{} // 様々な型に対応するため戻り値はinterface{}で定義
}

func GetBuilder(builderType string) iBuilder {
	if builderType == "bike" {
		return NewBikeBuilder()
	}
	if builderType == "manual" {
		return NewManualBuilder()
	}
	return nil
}

Director

package builder

// BuilderのAPIを用いてインスタンスを生成する
// Builderの具象クラスには依存しない
type Director struct {
	builder iBuilder
}

func NewDirector(b iBuilder) *Director {
	return &Director{builder: b}
}

func (d *Director) SetBuilder(b iBuilder) {
	d.builder = b
}

func (d *Director) Build() interface{} {
	d.builder.SetHandle()
	d.builder.SetTireSize()
	d.builder.SetFrame()
	// 最終的なオブジェクトはビルダーで生成したものを返す
	return d.builder.Build()
}

product

package builder

type bike struct {
	handle string
	tireSize int
	frame string
}
package builder

type manual struct {
	handlePage string
	tirePage string
	framePage string
}

concreteBuilder

package builder

type BikeBuilder struct {
	bike
}

func NewBikeBuilder() *BikeBuilder {
	return &BikeBuilder{}
}

func (b *BikeBuilder) SetHandle() {
	b.handle = "bike handle"
}

func (b *BikeBuilder) SetTireSize() {
	b.tireSize = 30
}

func (b *BikeBuilder) SetFrame() {
	b.frame = "bike frame"
}

func (b *BikeBuilder) Build() interface{} {
	return bike{
		handle: b.handle,
		tireSize: b.tireSize,
		frame: b.frame,
	}
}
package builder

type ManualBuilder struct {
	manual
}

func NewManualBuilder() *ManualBuilder {
	return &ManualBuilder{}
}

func (b *ManualBuilder) SetHandle() {
	b.handlePage = "handel manual"
}

func (b *ManualBuilder) SetTireSize() {
	b.tirePage = "tire manual"
}

func (b *ManualBuilder) SetFrame() {
	b.framePage = "frame manual"
}

func (b *ManualBuilder) Build() interface{} {
	return manual{
		handlePage: b.handlePage,
		tirePage: b.tirePage,
		framePage: b.framePage,
	}
}

動作確認

package main

import (
	"fmt"
	. "builder/builder"
)

func main() {
	bb := GetBuilder("bike")
	d := NewDirector(bb)
	bike := d.Build()
	fmt.Printf("Bike: %+v\n", bike)

	mb := GetBuilder("manual")
	d.SetBuilder(mb)
	manual := d.Build()
	fmt.Printf("Manual: %+v\n", manual)
}
>> go run .
Bike: {handle:bike handle tireSize:30 frame:bike frame}
Manual: {handlePage:handel manual tirePage:tire manual framePage:frame manual}

コメント

タイトルとURLをコピーしました