Command Pattern
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 91 of 107.
The Command pattern encapsulates a request as an object, allowing you to parameterize operations, queue them, or support undo functionality. While Strategy encapsulates algorithms, Command encapsulates entire actions along with their parameters.
In Go, we define a Command interface with an Execute method, then create concrete commands that hold all information needed to perform an action:
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"
}An invoker stores and executes commands without knowing what they do:
type RemoteControl struct {
command Command
}
func (r *RemoteControl) SetCommand(c Command) {
r.command = c
}
func (r *RemoteControl) PressButton() string {
return r.command.Execute()
}The invoker is completely decoupled from the receiver (the Light):
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 offCommand is ideal for implementing undo/redo systems, task queues, or macro recording where you need to store, delay, or replay operations.
Challenge
EasyLet's build a text editor with undo functionality using the Command pattern! You'll create commands that encapsulate text operations, allowing the editor to execute actions and reverse them—a classic use case that demonstrates why commands are so powerful.
You'll organize your code across three files:
command.go: Define your command interface and concrete text editing commands.Create a
Commandinterface with two methods:Execute() stringto perform the action andUndo() stringto reverse it.Implement two command types that operate on a
*TextEditor:InsertCommand— holds the editor pointer and text to insert. Execute appends the text to the editor's content and returnsInserted: [text]. Undo removes that text from the end and returnsUndone insert: [text]DeleteCommand— holds the editor pointer and a count of characters to delete from the end. Execute removes those characters (storing them for undo) and returnsDeleted: [removed text]. Undo restores them and returnsUndone delete: [restored text]
editor.go: Create your text editor (the receiver) and the invoker that manages command history.Build a
TextEditorstruct with aContentfield (string) and methods toAppend(text string)andDeleteLast(count int) string(returns the deleted text).Build an
EditorInvokerstruct that holds a slice of executed commands as history. Add these methods:ExecuteCommand(cmd Command) string— executes the command, adds it to history, and returns the resultUndoLast() string— removes the last command from history, calls its Undo, and returns the result. If history is empty, returnNothing to undo
main.go: Demonstrate your command system with a series of operations.Read a count of operations. For each operation, read the operation type (
insert,delete, orundo). For insert, also read the text to insert. For delete, read the character count. Execute each operation through your invoker and print the result. After all operations, print the final editor content asFinal: [content].
The following inputs will be provided:
- Line 1: Number of operations
- For each operation: operation type, then additional data if needed (text for insert, count for delete)
For example, given:
5
insert
Hello
insert
World
delete
3
undo
undoYour output should be:
Inserted: Hello
Inserted: World
Deleted: rld
Undone delete: rld
Undone insert: World
Final: HelloAnd given:
4
insert
Go
insert
Lang
undo
insert
!Your output should be:
Inserted: Go
Inserted: Lang
Undone insert: Lang
Inserted: !
Final: Go!And given:
2
undo
insert
TestYour output should be:
Nothing to undo
Inserted: Test
Final: TestNotice how each command encapsulates everything needed to both perform and reverse its action. The invoker doesn't know what the commands do—it just executes them and maintains history for undo support!
Cheat sheet
The Command pattern encapsulates a request as an object, allowing you to parameterize operations, queue them, or support undo functionality.
Define a Command interface with an Execute method:
type Command interface {
Execute() string
}Create concrete commands that hold all information needed to perform an action:
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"
}An invoker stores and executes commands without knowing what they do:
type RemoteControl struct {
command Command
}
func (r *RemoteControl) SetCommand(c Command) {
r.command = c
}
func (r *RemoteControl) PressButton() string {
return r.command.Execute()
}Usage example showing decoupling between invoker and receiver:
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 offFor undo functionality, add an Undo method to the Command interface and maintain command history in the invoker.
Try it yourself
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Read number of operations
line, _ := reader.ReadString('\n')
numOps, _ := strconv.Atoi(strings.TrimSpace(line))
// Create the editor and invoker
editor := &TextEditor{}
invoker := &EditorInvoker{}
for i := 0; i < numOps; i++ {
// Read operation type
opLine, _ := reader.ReadString('\n')
opType := strings.TrimSpace(opLine)
var result string
switch opType {
case "insert":
// Read text to insert
textLine, _ := reader.ReadString('\n')
text := strings.TrimSuffix(textLine, "\n")
// TODO: Create an InsertCommand and execute it through the invoker
_ = text
_ = editor
case "delete":
// Read count of characters to delete
countLine, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countLine))
// TODO: Create a DeleteCommand and execute it through the invoker
_ = count
case "undo":
// TODO: Call UndoLast on the invoker
}
fmt.Println(result)
}
// Print final content
fmt.Printf("Final: %s\n", editor.Content)
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser11Standard Library & OOP
io.Reader & io.Writersort.Interfacefmt.Stringer Interfaceencoding/json with Structshttp.Handler InterfaceRecap - REST API Models14Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite PatternMiddleware as Decorator3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker Pool