Menu
Coddy logo textTech

Abstract Factory パターン

CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 88/107。

Abstract Factoryパターンは、具体的な型を指定することなく、関連するオブジェクトのファミリを作成することで、Factoryパターンを拡張します。単一のファクトリ関数の代わりに、複数の具体的なファクトリが実装するファクトリインターフェースを定義し、それぞれが一貫した関連オブジェクトのセットを生成します。

ボタンやチェックボックスを作成する必要があるUIツールキットを考えてみましょう。オペレーティングシステムによって実装は異なりますが、コンポーネントは一貫して連携して動作する必要があります:

type Button interface {
    Render() string
}

type Checkbox interface {
    Check() string
}

// 抽象ファクトリインターフェース
type GUIFactory interface {
    CreateButton() Button
    CreateCheckbox() Checkbox
}

各具象ファクトリは、関連するコンポーネントのファミリーを生成します:

// Windows ファミリー
type WindowsButton struct{}
func (w WindowsButton) Render() string { return "Windows Button" }

type WindowsCheckbox struct{}
func (w WindowsCheckbox) Check() string { return "Windows Checkbox" }

type WindowsFactory struct{}
func (w WindowsFactory) CreateButton() Button { return WindowsButton{} }
func (w WindowsFactory) CreateCheckbox() Checkbox { return WindowsCheckbox{} }

// Mac ファミリー
type MacButton struct{}
func (m MacButton) Render() string { return "Mac Button" }

type MacCheckbox struct{}
func (m MacCheckbox) Check() string { return "Mac Checkbox" }

type MacFactory struct{}
func (m MacFactory) CreateButton() Button { return MacButton{} }
func (m MacFactory) CreateCheckbox() Checkbox { return MacCheckbox{} }

クライアントコードはファクトリインターフェースを介して動作するため、具体的な実装に依存しません:

func BuildUI(factory GUIFactory) {
    button := factory.CreateButton()
    checkbox := factory.CreateCheckbox()
    fmt.Println(button.Render(), checkbox.Check())
}

システムが関連する製品の複数のファミリーを扱う必要があり、同じファミリーのコンポーネントが確実に一緒に使用されるようにしたい場合に、Abstract Factoryを使用します。これは単純なFactoryよりも複雑であるため、製品ファミリーを管理する必要が本当にある場合にのみ検討してください。

challenge icon

チャレンジ

簡単

Abstract Factory パターンを使用して家具店システムを構築しましょう!関連する家具アイテムのファミリーを生成するファクトリを作成します。各ファクトリは一貫したスタイル(Modern または Vintage)を生成し、同じファクトリから作られた椅子とテーブルのデザインが調和するようにします。

コードは以下の3つのファイルに分けて構成します:

  • furniture.go: 製品のインターフェースと具体的な家具の型を定義します。

    2つの製品インターフェースを作成してください:

    • SitOn() string メソッドを持つ Chair
    • PlaceItems() string メソッドを持つ Table

    2つの家具ファミリーを実装してください:

    • Modern ファミリー: ModernChairSitting on a sleek modern chair を返し、ModernTablePlacing items on a glass modern table を返します。
    • Vintage ファミリー: VintageChairSitting on an ornate vintage chair を返し、VintageTablePlacing items on a wooden vintage table を返します。
  • factory.go: 抽象ファクトリインターフェースと具体的なファクトリを定義します。

    2つのメソッドを持つ FurnitureFactory インターフェースを作成してください:

    • CreateChair() Chair
    • CreateTable() Table

    2つの具体的なファクトリを実装してください:

    • ModernChairModernTable を生成する ModernFactory
    • VintageChairVintageTable を生成する VintageFactory

    また、スタイル(modern または vintage)に基づいて適切なファクトリを返す関数 GetFactory(style string) FurnitureFactory を作成してください。認識されないスタイルの場合は、デフォルトで ModernFactory を返します。

  • main.go: ファクトリを使用して部屋に家具を配置します。

    ファクトリを使用して椅子とテーブルを作成し、両方のアクションを別々の行にフォーマットした文字列として返す関数 FurnishRoom(factory FurnitureFactory) string を作成してください。

    スタイルの入力を読み取り、適切なファクトリを取得し、部屋に家具を配置して、その結果を出力します。

以下の入力が提供されます:

  • 1行目:家具のスタイル(modernvintage、またはその他)

例えば、次のように入力された場合:

modern

出力は以下のようになります:

Sitting on a sleek modern chair
Placing items on a glass modern table

また、次のように入力された場合:

vintage

出力は以下のようになります:

Sitting on an ornate vintage chair
Placing items on a wooden vintage table

また、次のように入力された場合:

rustic

出力は以下のようになります:

Sitting on a sleek modern chair
Placing items on a glass modern table

FurnishRoom 関数がインターフェースを通じてどのファクトリとも連携できることに注目してください。この関数は、モダンな家具を作っているのかヴィンテージな家具を作っているのかを知る必要がありません。これにより、部屋のすべての家具が同じファミリーから提供され、一貫したスタイルが維持されることが保証されます!

チートシート

Abstract Factory パターンは、具体的な型を指定することなく、関連するオブジェクトのファミリーを作成します。これは、複数の具体的なファクトリが実装するファクトリインターフェースを定義し、各ファクトリが一貫した関連オブジェクトのセットを生成します。

構造

各オブジェクトタイプに対してプロダクトインターフェースを定義します:

type Button interface {
    Render() string
}

type Checkbox interface {
    Check() string
}

各プロダクトタイプを作成するためのメソッドを宣言する抽象ファクトリインターフェースを作成します:

type GUIFactory interface {
    CreateButton() Button
    CreateCheckbox() Checkbox
}

具体的な実装

各ファミリーが一貫したスタイリングや動作を持つ、具体的なプロダクトファミリーを実装します:

// Windows ファミリー
type WindowsButton struct{}
func (w WindowsButton) Render() string { return "Windows Button" }

type WindowsCheckbox struct{}
func (w WindowsCheckbox) Check() string { return "Windows Checkbox" }

type WindowsFactory struct{}
func (w WindowsFactory) CreateButton() Button { return WindowsButton{} }
func (w WindowsFactory) CreateCheckbox() Checkbox { return WindowsCheckbox{} }

// Mac ファミリー
type MacButton struct{}
func (m MacButton) Render() string { return "Mac Button" }

type MacCheckbox struct{}
func (m MacCheckbox) Check() string { return "Mac Checkbox" }

type MacFactory struct{}
func (m MacFactory) CreateButton() Button { return MacButton{} }
func (m MacFactory) CreateCheckbox() Checkbox { return MacCheckbox{} }

クライアントコード

クライアントコードはファクトリインターフェースを介して動作し、具体的な実装からは独立したままになります:

func BuildUI(factory GUIFactory) {
    button := factory.CreateButton()
    checkbox := factory.CreateCheckbox()
    fmt.Println(button.Render(), checkbox.Check())
}

いつ使用するか

システムが関連するプロダクトの複数のファミリーを扱う必要があり、同じファミリーのコンポーネントが一緒に使用されることを保証したい場合に Abstract Factory を使用します。これは単純な Factory よりも複雑であるため、プロダクトファミリーを管理する必要が本当にある場合にのみ使用してください。

自分で試してみよう

package main

import "fmt"

// FurnishRoom はファクトリを使用して家具を作成し、その結果を返します
// TODO: この関数を実装してください
// - ファクトリを使用して椅子とテーブルを作成します
// - 1行目に `SitOn()` の結果、2行目に `PlaceItems()` の結果を含む文字列を返します
func FurnishRoom(factory FurnitureFactory) string {
	// TODO: ファクトリを使用して椅子とテーブルを作成してください
	// TODO: 両方のアクションを別々の行に含めた、フォーマット済み文字列を返してください
	return ""
}

func main() {
	var style string
	fmt.Scanln(&style)

	// TODO: スタイルに基づいて適切なファクトリを取得してください
	// TODO: ファクトリを使用して部屋に家具を配置してください
	// TODO: 結果を出力してください
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン