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つを選択する必要がある場合に理想的です。一般的なユースケースには、ソートアルゴリズム、圧縮方法、バリデーションルールなどがあります。
チャレンジ
簡単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: 実行時に戦略を切り替えるデモンストレーションを行います。テキストを読み込み、次にフォーマット操作の回数を読み込みます。各操作について、フォーマットタイプ(
upper、snake、または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") に基づいて、
// プロセッサに適切なフォーマッタを設定し、
// 処理されたテキストを出力する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー