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

Goデザインパターン

Abstract Factoryパターンとは

生成に関するデザインパターンの一つ。関連したオブジェクトの集まりを、具象クラスを指定することなく生成することを可能にするパターン。

例えばPC, モバイル端末のそれぞれに特化したGUIを生成する場合を考える。GUIを生成する際に必要な部品, 様式として下記のように考えることができる。

  • 部品: Button, CheckBox etc…
  • 様式: PC用,IPhone用, IPad用 etc…

仮にPC用のGUIを生成しようとした場合、すべての部品(PC用Button, PC用CheckBox…)を生成する必要がある。このような時に具象クラスを呼び出すことなくそれぞれの様式に沿ったオブジェクトを生成することを可能にしたのがAbstract Factoryパターンになる。

各部品は様式(PC, IPhone,…)ごとに用意されたファクトリーで生成される。
クライアント側はファクトリー・部品ともに抽象インターフェースを利用して実装される。そのためファクトリーや部品の型・実装を変更したとしても問題なく動作することができる。

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

  • ファクトリから得られる部品同士は互換であることが保証される。
  • 具象とクライアント側処理の密結合を防ぐことができる。
  • 部品作成コードが1箇所にまとめられる。(単一責任の原則)
  • 新しい変種(ファクトリー)を追加してもクライアント側コードは問題なく動作する。(開放閉鎖の原則)

クラス図

  1. AbstractProduct
    AbstractFactoryによって生成される抽象的な部品や製品のインターフェースを宣言する。
  2. ConcreteProduct
    AbstractFactoryに宣言されたインターフェースを実装する。個々のAbstractProductはすべての変種(ProductA1,A2…)について実装されている必要がある。
  3. AbstractFactory
    AbstractProductのそれぞれを生成するためのインターフェースを宣言する。
  4. ConcreteFactory
    AbstractFactoryで宣言されたAbstractProductを生成するためのインターフェースを実装する。
    個々のConcreteFactoryは特定の種類(ConcreteProductA1,B1..)にのみ対応している。

実装

AbstractProduct

package abstract_factory

type ICheckBox interface {
	SetHeight(height int)
	SetWidth(width int)
	GetHeight() int
	GetWidth() int
}

type CheckBox struct {
	Height int
	Width int
}

func (c *CheckBox) SetHeight(height int) {
	c.Height = height
}

func (c *CheckBox) SetWidth(width int) {
	c.Width = width
}

func (c *CheckBox) GetHeight() int {
	return c.Height
}

func (c *CheckBox) GetWidth() int {
	return c.Width
}
package abstract_factory

type IButton interface {
	SetColor(color string)
	SetText(text string)
	GetColor() string
	GetText() string
}

type Button struct {
	Color string
	Text string
}

func (b *Button) SetColor(color string) {
	b.Color = color
}

func (b *Button) SetText(text string) {
	b.Text = text
}

func (b *Button) GetColor() string {
	return b.Color
}

func (b *Button) GetText() string {
	return b.Text
}

ConcreteProduct

package pcfactory

import (
	. "abstract_factory/abstract_factory"
)

type pcCheckBox struct {
	CheckBox
}
package pcfactory

import (
	. "abstract_factory/abstract_factory"
)

type pcButton struct {
	Button
}
package mobilefactory

import (
	. "abstract_factory/abstract_factory"
)

type mobileCheckBox struct {
	CheckBox
}
package mobilefactory

import (
	. "abstract_factory/abstract_factory"
)

type mobileButton struct {
	Button
}

AbstractFactory

package abstract_factory

type IGuiFactory interface {
	createCheckBox() ICheckBox // 戻り値は抽象部品
	createButton() IButton // 戻り値は抽象部品
}

ConcreteFactory

package pcfactory

import (
	. "abstract_factory/abstract_factory"
)

// PC用GUI部品を生成するファクトリー
type pcGuiFactory struct {}

func NewGetPcGuiFactory() *pcGuiFactory {
	return &pcGuiFactory{}
}

func (f *pcGuiFactory) CreateCheckBox() ICheckBox { // 戻り値は抽象部品
	return &pcCheckBox{
		CheckBox: CheckBox{Height: 20, Width: 20},
	}
}

func (f *pcGuiFactory) CreateButton() IButton { // 戻り値は抽象部品
	return &pcButton{
		Button: Button{Color: "red", Text: "PC Button"},
	}
}
package mobilefactory

import (
	. "abstract_factory/abstract_factory"
)

// モバイル用GUI部品を生成するファクトリー
type mobileGuiFactory struct {}

func NewGetMobileGuiFactory() *mobileGuiFactory {
	return &mobileGuiFactory{}
}

func (f *mobileGuiFactory) CreateCheckBox() ICheckBox { // 戻り値は抽象部品
	return &mobileCheckBox{
		CheckBox: CheckBox{Height: 10, Width: 10},
	}
}

func (f *mobileGuiFactory) CreateButton() IButton { // 戻り値は抽象部品
	return &mobileButton{
		Button: Button{Color: "blue", Text: "Mobile Button"},
	}
}

動作確認

package main

import (
	"fmt"
	. "abstract_factory/abstract_factory"
	. "abstract_factory/pcfactory"
	. "abstract_factory/mobilefactory"
)

func getFactory(factoryType string) IGuiFactory {
	if factoryType == "pc" {
		return NewGetPcGuiFactory()
	}
	if factoryType == "mobile" {
		return NewGetMobileGuiFactory()
	}
	return nil
}

type app struct {
	factory IGuiFactory
}

func (a *app) createGUI() {
	c := a.factory.CreateCheckBox()
	b := a.factory.CreateButton()
	fmt.Printf("CheckBox={height: %d, width: %d}\n", c.GetHeight(), c.GetWidth())
	fmt.Printf("Button={color: %s, text: %s}\n", b.GetColor(), b.GetText())
}

func main() {
	types := []string{"pc", "mobile"}

	// クライアントからは抽象型(IGuiFactory, ICheckBox, IButton)を通して処理を行う。
	// これにより仮に新たな型・ファクトリが追加されてもクライアント側は問題なく動作する。
	for _, t := range types {
		fmt.Println(t)
		f := getFactory(t)
		a := app{factory: f}
		a.createGUI()
	}
}
>> go run .
pc
CheckBox={height: 20, width: 20}
Button={color: red, text: PC Button}
mobile
CheckBox={height: 10, width: 10}
Button={color: blue, text: Mobile Button}

補足

  • 新しいファクトリーを追加することは容易。しかし新しい部品を追加する場合は、各ファクトリの修正やクライアント側の修正が必要になる可能性がある。

コメント

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