Menu
Coddy logo textTech

Strategy パターン

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

Strategyパターンは、一連のアルゴリズムを定義し、それぞれをカプセル化して、実行時にそれらを入れ替え可能にします。複数のオブジェクトが1つのイベントに反応するObserverとは異なり、Strategyは単一のオブジェクトが異なる振る舞いを動的に切り替えることを可能にします。

Goでは、アルゴリズムのインターフェースを定義し、異なる実装をコンテキスト構造体に注入することで、Strategyパターンを実装します:

type PaymentStrategy interface {
    Pay(amount float64) string
}

type CreditCard struct{}
func (c CreditCard) Pay(amount float64) string {
    return fmt.Sprintf("Paid %.2f via Credit Card", amount)
}

type PayPal struct{}
func (p PayPal) Pay(amount float64) string {
    return fmt.Sprintf("Paid %.2f via PayPal", amount)
}

context構造体はstrategyインターフェースへの参照を保持しており、contextのコードを変更することなくアルゴリズムを入れ替えることができます。

type Checkout struct {
    strategy PaymentStrategy
}

func (c *Checkout) SetStrategy(s PaymentStrategy) {
    c.strategy = s
}

func (c *Checkout) ProcessPayment(amount float64) string {
    return c.strategy.Pay(amount)
}

これで、実行時に支払い方法を変更できるようになります:

checkout := &Checkout{}

checkout.SetStrategy(CreditCard{})
fmt.Println(checkout.ProcessPayment(100.00))  // クレジットカードで100.00を支払いました

checkout.SetStrategy(PayPal{})
fmt.Println(checkout.ProcessPayment(50.00))   // PayPalで50.00を支払いました

Strategyは、操作を実行するための複数の方法があり、ユーザーの入力、設定、または実行時の条件に基づいてそのうちの1つを選択する必要がある場合に理想的です。一般的なユースケースには、ソートアルゴリズム、圧縮方法、バリデーションルールなどがあります。

challenge icon

チャレンジ

簡単

Strategy パターンを使用してテキストフォーマットシステムを構築しましょう!実行時に切り替え可能な異なるフォーマット戦略を作成することで、同じテキストプロセッサがコアコードを変更することなく、異なる出力スタイルを生成できるようにします。

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

  • formatter.go: ストラテジーインターフェースと具体的なフォーマット戦略を定義します。

    戦略のルールに従ってテキストを変換する Format(text string) string メソッドを持つ TextFormatter インターフェースを作成します。

    3つのフォーマット戦略を実装してください:

    • UppercaseFormatter — テキストをすべて大文字に変換します
    • SnakeCaseFormatter — スペースをアンダースコアに変換し、テキストを小文字にします
    • TitleFormatter — 各単語の最初の文字を大文字にします(単語はスペースで区切られます)
  • processor.go: フォーマット戦略を使用するコンテキスト構造体を作成します。

    TextFormatter 戦略を保持する TextProcessor 構造体を構築します。以下のメソッドを追加してください:

    • SetFormatter(f TextFormatter): アクティブなフォーマット戦略を変更します
    • Process(text string) string: 現在のフォーマッタをテキストに適用します

    また、初期フォーマッタが設定されていないプロセッサを返す NewTextProcessor() コンストラクタも作成してください。

  • main.go: 実行時に戦略を切り替えるデモンストレーションを行います。

    テキストを読み込み、次にフォーマット操作の回数を読み込みます。各操作について、フォーマットタイプ(uppersnake、または title)を読み込み、プロセッサに適切なフォーマッタを設定し、元のテキストを処理して結果を出力します。

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

  • 1行目:フォーマットするテキスト
  • 2行目:フォーマット操作の数
  • 続く行:各操作のフォーマットタイプ

例えば、以下の場合:

hello world today
3
upper
snake
title

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

HELLO WORLD TODAY
hello_world_today
Hello World Today

また、以下の場合:

Go Programming Language
2
snake
upper

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

go_programming_language
GO PROGRAMMING LANGUAGE

また、以下の場合:

design patterns are useful
1
title

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

Design Patterns Are Useful

同じ TextProcessor が、フォーマッタ戦略を入れ替えるだけで、どのように全く異なる出力を生成するかに注目してください。プロセッサは、自分がどの具体的なフォーマッタを使用しているかを知る必要も、気にする必要もありません。単に現在設定されている戦略に処理を委譲するだけです!

チートシート

Strategy パターンは、一連のアルゴリズムを定義し、それぞれをカプセル化して、実行時にそれらを交換可能にします。これにより、単一のオブジェクトが動的に異なる振る舞いを切り替えることができます。

アルゴリズムのインターフェースを定義します:

type PaymentStrategy interface {
    Pay(amount float64) string
}

具体的なストラテジーを実装します:

type CreditCard struct{}
func (c CreditCard) Pay(amount float64) string {
    return fmt.Sprintf("Paid %.2f via Credit Card", amount)
}

type PayPal struct{}
func (p PayPal) Pay(amount float64) string {
    return fmt.Sprintf("Paid %.2f via PayPal", amount)
}

ストラテジーインターフェースへの参照を保持するコンテキスト構造体を作成します:

type Checkout struct {
    strategy PaymentStrategy
}

func (c *Checkout) SetStrategy(s PaymentStrategy) {
    c.strategy = s
}

func (c *Checkout) ProcessPayment(amount float64) string {
    return c.strategy.Pay(amount)
}

実行時にストラテジーを変更します:

checkout := &Checkout{}

checkout.SetStrategy(CreditCard{})
fmt.Println(checkout.ProcessPayment(100.00))  // クレジットカードで 100.00 支払い済み

checkout.SetStrategy(PayPal{})
fmt.Println(checkout.ProcessPayment(50.00))   // PayPalで 50.00 支払い済み

Strategy パターンは、ある操作を実行する方法が複数あり、ユーザーの入力、設定、または実行時の条件に基づいてそのうちの1つを選択する必要がある場合に最適です。

自分で試してみよう

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// フォーマットするテキストを読み込む
	text, _ := reader.ReadString('\n')
	text = strings.TrimSpace(text)
	
	// 操作の回数を読み込む
	countStr, _ := reader.ReadString('\n')
	count, _ := strconv.Atoi(strings.TrimSpace(countStr))
	
	// 新しいテキストプロセッサを作成する
	processor := NewTextProcessor()
	
	// 各フォーマット操作を処理する
	for i := 0; i < count; i++ {
		formatType, _ := reader.ReadString('\n')
		formatType = strings.TrimSpace(formatType)
		
		// TODO: formatType ("upper", "snake", または "title") に基づいて、
		// プロセッサに適切なフォーマッタを設定し、
		// 処理されたテキストを出力する
		
	}
}
quiz icon腕試し

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

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