Commandパターン
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 91/107。
Command パターンは、リクエストをオブジェクトとしてカプセル化することで、操作のパラメータ化、キューへの追加、または取り消し(undo)機能のサポートを可能にします。Strategy がアルゴリズムをカプセル化するのに対し、Command はアクション全体をそのパラメータと共にカプセル化します。
Goでは、Executeメソッドを持つCommandインターフェースを定義し、アクションの実行に必要なすべての情報を保持する具体的なコマンドを作成します:
type Command interface {
Execute() string
}
type Light struct {
IsOn bool
}
type TurnOnCommand struct {
light *Light
}
func (c *TurnOnCommand) Execute() string {
c.light.IsOn = true
return "Light turned on"
}
type TurnOffCommand struct {
light *Light
}
func (c *TurnOffCommand) Execute() string {
c.light.IsOn = false
return "Light turned off"
}インボーカーは、コマンドの内容を知ることなく、コマンドを保持し実行します:
type RemoteControl struct {
command Command
}
func (r *RemoteControl) SetCommand(c Command) {
r.command = c
}
func (r *RemoteControl) PressButton() string {
return r.command.Execute()
}インボーカーはレシーバー(Light)から完全に切り離されています:
light := &Light{}
remote := &RemoteControl{}
remote.SetCommand(&TurnOnCommand{light: light})
fmt.Println(remote.PressButton()) // ライトが点灯しました
remote.SetCommand(&TurnOffCommand{light: light})
fmt.Println(remote.PressButton()) // ライトが消灯しましたCommandは、操作の保存、遅延、または再実行が必要な、元に戻す/やり直し(undo/redo)システム、タスクキュー、またはマクロ記録の実装に最適です。
チャレンジ
簡単Commandパターンを使って、アンドゥ(元に戻す)機能を備えたテキストエディタを構築しましょう!テキスト操作をカプセル化したコマンドを作成することで、エディタがアクションを実行し、それを取り消すことができるようになります。これは、コマンドがいかに強力であるかを示す古典的なユースケースです。
コードは以下の3つのファイルに分けて構成します:
command.go: コマンドインターフェースと具体的なテキスト編集コマンドを定義します。アクションを実行する
Execute() stringと、それを取り消すUndo() stringの2つのメソッドを持つCommandインターフェースを作成します。*TextEditorに対して操作を行う2つのコマンドタイプを実装します:InsertCommand— エディタへのポインタと挿入するテキストを保持します。Executeはエディタのコンテンツにテキストを追加し、Inserted: [text]を返します。Undoはそのテキストを末尾から削除し、Undone insert: [text]を返します。DeleteCommand— エディタへのポインタと末尾から削除する文字数を保持します。Executeはそれらの文字を削除し(アンドゥのために保存しておく)、Deleted: [removed text]を返します。Undoはそれらを復元し、Undone delete: [restored text]を返します。
editor.go: テキストエディタ(レシーバ)と、コマンドの履歴を管理するインボーカー(起動者)を作成します。Contentフィールド(string)と、Append(text string)およびDeleteLast(count int) string(削除されたテキストを返す)メソッドを持つTextEditor構造体を構築します。実行されたコマンドのスライスを履歴として保持する
EditorInvoker構造体を構築します。以下のメソッドを追加してください:ExecuteCommand(cmd Command) string— コマンドを実行し、履歴に追加して、その結果を返します。UndoLast() string— 履歴から最後のコマンドを取り出し、そのUndoを呼び出して、結果を返します。履歴が空の場合は、Nothing to undoを返します。
main.go: 一連の操作を通じてコマンドシステムを実演します。操作の回数を読み取ります。各操作について、操作タイプ(
insert、delete、またはundo)を読み取ります。insertの場合は挿入するテキストも読み取ります。deleteの場合は文字数を読み取ります。インボーカーを通じて各操作を実行し、結果を出力します。すべての操作が終わった後、最終的なエディタのコンテンツをFinal: [content]として出力します。
以下の入力が提供されます:
- 1行目:操作の数
- 各操作:操作タイプ、必要に応じて追加データ(insertの場合はテキスト、deleteの場合はカウント)
例えば、以下の場合:
5
insert
Hello
insert
World
delete
3
undo
undo出力は以下のようになります:
Inserted: Hello
Inserted: World
Deleted: rld
Undone delete: rld
Undone insert: World
Final: Helloまた、以下の場合:
4
insert
Go
insert
Lang
undo
insert
!出力は以下のようになります:
Inserted: Go
Inserted: Lang
Undone insert: Lang
Inserted: !
Final: Go!また、以下の場合:
2
undo
insert
Test出力は以下のようになります:
Nothing to undo
Inserted: Test
Final: Test各コマンドが、アクションの実行と取り消しの両方に必要なすべてをどのようにカプセル化しているかに注目してください。インボーカーはコマンドが何をするかを知りません。ただコマンドを実行し、アンドゥをサポートするために履歴を維持するだけです!
チートシート
Command パターンは、リクエストをオブジェクトとしてカプセル化することで、操作のパラメータ化、キューへの追加、または取り消し(Undo)機能のサポートを可能にします。
Execute メソッドを持つ Command インターフェースを定義します:
type Command interface {
Execute() string
}アクションを実行するために必要なすべての情報を保持する具体的なコマンドを作成します:
type Light struct {
IsOn bool
}
type TurnOnCommand struct {
light *Light
}
func (c *TurnOnCommand) Execute() string {
c.light.IsOn = true
return "Light turned on"
}
type TurnOffCommand struct {
light *Light
}
func (c *TurnOffCommand) Execute() string {
c.light.IsOn = false
return "Light turned off"
}インボーカー(実行者)は、コマンドが何をするかを知ることなく、コマンドを保存して実行します:
type RemoteControl struct {
command Command
}
func (r *RemoteControl) SetCommand(c Command) {
r.command = c
}
func (r *RemoteControl) PressButton() string {
return r.command.Execute()
}インボーカーとレシーバーの間の疎結合を示す使用例:
light := &Light{}
remote := &RemoteControl{}
remote.SetCommand(&TurnOnCommand{light: light})
fmt.Println(remote.PressButton()) // Light turned on
remote.SetCommand(&TurnOffCommand{light: light})
fmt.Println(remote.PressButton()) // Light turned off取り消し(Undo)機能については、Command インターフェースに Undo メソッドを追加し、インボーカーでコマンドの履歴を保持します。
自分で試してみよう
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// 操作の数を読み込む
line, _ := reader.ReadString('\n')
numOps, _ := strconv.Atoi(strings.TrimSpace(line))
// editor と invoker を作成する
editor := &TextEditor{}
invoker := &EditorInvoker{}
for i := 0; i < numOps; i++ {
// 操作のタイプを読み込む
opLine, _ := reader.ReadString('\n')
opType := strings.TrimSpace(opLine)
var result string
switch opType {
case "insert":
// 挿入するテキストを読み込む
textLine, _ := reader.ReadString('\n')
text := strings.TrimSuffix(textLine, "\n")
// TODO: InsertCommand を作成し、invoker を通じて実行する
_ = text
_ = editor
case "delete":
// 削除する文字数を読み込む
countLine, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countLine))
// TODO: DeleteCommand を作成し、invoker を通じて実行する
_ = count
case "undo":
// TODO: invoker の UndoLast を呼び出す
}
fmt.Println(result)
}
// 最終的な内容を出力する
fmt.Printf("Final: %s\n", editor.Content)
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー11標準ライブラリとOOP
io.Reader と io.Writersort.Interfacefmt.Stringer インターフェースencoding/json と構造体http.Handler インターフェースまとめ:REST API モデル14デザインパターン パート2
CommandパターンAdapterパターンDecoratorパターンTemplate MethodパターンStateパターンCompositeパターンDecoratorとしてのMiddleware