Goでデザインパターン (Factory Methodパターン)

Goデザインパターン

Factory Methodパターンとは

Template Methodパターンでは処理の骨組みをテンプレートに切り出し、具体的な処理を他構造体で実装していた。これをインスタンス生成に適用したものがFactory Methodパターンになる。

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

  • オブジェクト生成手順とその具体的な処理を分離することで抽象度を高めることができる。
    →呼び出し側は具体的な処理を気にしなくてよくなる。また生成手順に変更があった場合でも、呼び出し側に修正が必要ない。

クラス図

実装

framework パッケージ (抽象クラス)

package framework

type Product interface {
	Use()
}
package framework

type Factory interface {
	CreateProduct(owner string) Product
	RegisterProduct(product *Product)
}
package framework

// 構造体作成のテンプレートを定義している構造体
// Factoryインターフェースを実装した構造体を利用してProduct構造体を作成する
type TemplateFactory struct {
	factory Factory
}

func NewTemplateFactory(factory Factory) *TemplateFactory {
	f := TemplateFactory{factory: factory}
	return &f
}

// Factory Method (Template Method)
func (t *TemplateFactory) Create(owner string) Product {
	p := t.factory.CreateProduct(owner)
	t.factory.RegisterProduct(&p)
	return p
}

idcard パッケージ (具象クラス)

package idcard

import (
	"fmt"
)

type IDCard struct {
	owner string
}

// idcardパッケージ以外から生成できないように外部には非公開にする
// →生成は必ずIDCardFactory経由で行うようにする
func newIDCard(owner string) IDCard {
	fmt.Println(owner + "のカードを作ります。")
	i := IDCard{owner: owner}
	return i
}

func (i IDCard) Use() {
	fmt.Println(i.owner + "のカードを使います。")
}

func (i *IDCard) GetOwner() string {
	return i.owner
}
package idcard

import (
	. "factory_method/framework"
)

type IDCardFactory struct {
	owners []string
}

func NewIDCardFactory() *IDCardFactory {
	f := IDCardFactory{owners: []string{}}
	return &f
}

func (i *IDCardFactory) CreateProduct(owner string) Product {
	return newIDCard(owner)
}

func (i *IDCardFactory) RegisterProduct(product *Product) {
	p := (*product).(IDCard)
	i.owners = append(i.owners, p.GetOwner())
}

func (i *IDCardFactory) GetOwners() []string {
	return i.owners
}

動作確認

package main

import (
	. "factory_method/framework"
	. "factory_method/idcard"
)

func main() {
	f := NewTemplateFactory(NewIDCardFactory())

	c1 := f.Create("田中太郎")
	c2 := f.Create("名無しの権兵衛")
	c3 := f.Create("山田花子")

	c1.Use()
	c2.Use()
	c3.Use()
}
田中太郎のカードを作ります。
名無しの権兵衛のカードを作ります。
山田花子のカードを作ります。
田中太郎のカードを使います。
名無しの権兵衛のカードを使います。
山田花子のカードを使います。

補足

frameworkパッケージ中ではidcardパッケージをimportしていない。つまりframeworkパッケージは具象であるidcardパッケージには全く依存していない
したがって新しいインスタンスを作成するファクトリを用意する場合でも、frameworkパッケージに修正が必要ない。

新たにCellPhoneFactoryを追加してみる。IDCardFactoryとは少し異なり、こちらはIDとOwnerの対応関係をMapで保持するようにする。

package cellphone

import (
	"fmt"
)

type CellPhone struct {
	owner string
}

func newCellPhone(owner string) CellPhone {
	fmt.Printf("%sのスマホを作成しました。\n", owner)
	a := CellPhone{owner: owner}
	return a
}

func (i CellPhone) Use() {
	fmt.Printf("%sのスマホを使います。\n", i.owner)
}

func (i *CellPhone) GetOwner() string {
	return i.owner
}
package cellphone

import (
	. "factory_method/framework"
)

type CellPhoneFactory struct {
	owners map[int]string
	serial int
}

func NewCellPhoneFactory() *CellPhoneFactory {
	f := CellPhoneFactory{owners: map[int]string{}, serial: 0}
	return &f
}

func (i *CellPhoneFactory) CreateProduct(owner string) Product {
	return newCellPhone(owner)
}

func (i *CellPhoneFactory) RegisterProduct(product *Product) {
	p := (*product).(CellPhone)
	i.owners[i.serial] = p.GetOwner()
	i.serial++
}

func (i *CellPhoneFactory) GetOwners() map[int]string {
	return i.owners
}
package main

import (
	. "factory_method/framework"
	. "factory_method/idcard"
	. "factory_method/cellphone"
	"fmt"
)

func main() {
	// ID Card 生成
	idf := NewIDCardFactory()
	f := NewTemplateFactory(idf)

	c1 := f.Create("田中太郎")
	c2 := f.Create("名無しの権兵衛")
	c3 := f.Create("山田花子")

	c1.Use()
	c2.Use()
	c3.Use()

	fmt.Println(idf.GetOwners())

	fmt.Println()

	// Cell Phone 生成
	cpf := NewCellPhoneFactory()
	cf := NewTemplateFactory(cpf)

	cf1 := cf.Create("田中太郎")
	cf2 := cf.Create("名無しの権兵衛")
	cf3 := cf.Create("山田花子")

	cf1.Use()
	cf2.Use()
	cf3.Use()

	fmt.Println(cpf.GetOwners())
}
田中太郎のカードを作ります。
名無しの権兵衛のカードを作ります。
山田花子のカードを作ります。
田中太郎のカードを使います。
名無しの権兵衛のカードを使います。
山田花子のカードを使います。
[田中太郎 名無しの権兵衛 山田花子]

田中太郎のスマホを作成しました。
名無しの権兵衛のスマホを作成しました。
山田花子のスマホを作成しました。
田中太郎のスマホを使います。
名無しの権兵衛のスマホを使います。
山田花子のスマホを使います。
map[0:田中太郎 1:名無しの権兵衛 2:山田花子]

main.goと実行結果を見ればわかるように、Factoryの実装に関わらず同じ呼び出し方で生成処理を実行することができている。

コメント

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