命令模式
本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 91 课,共 107 课。
命令模式将请求封装为对象,让你可以对操作进行参数化、将操作排队,或支持撤销功能。策略模式封装算法,而命令模式将整个操作及其参数封装起来。
在 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 非常适合用于实现撤销/重做系统、任务队列或宏录制。
挑战
简单让我们使用命令模式构建一个支持撤销功能的文本编辑器吧!你将创建封装文本操作的命令,让编辑器能够执行和撤销操作。这是一个经典用例,能展示命令模式为何如此强大。
你将把代码分成三个文件:
command.go:定义命令接口和具体的文本编辑命令。创建一个
Command接口,包含两个方法:Execute() string用于执行操作,Undo() string用于撤销操作。实现两个操作
*TextEditor的命令类型:InsertCommand:保存编辑器指针和要插入的文本。执行时,将文本追加到编辑器内容末尾,并返回Inserted: [text]。撤销时,从末尾移除这段文本,并返回Undone insert: [text]DeleteCommand:保存编辑器指针,以及要从末尾删除的字符数。执行时,移除这些字符(并保存它们以便撤销),然后返回Deleted: [removed text]。撤销时,恢复这些字符,然后返回Undone delete: [restored text]
editor.go:创建文本编辑器(接收者)以及管理命令历史记录的调用者。构建一个
TextEditor结构体,包含Content字段(字符串),以及Append(text string)和DeleteLast(count int) string方法(返回已删除的文本)。构建一个
EditorInvoker结构体,用切片保存已执行的命令作为历史记录。添加以下方法:ExecuteCommand(cmd Command) string:执行命令,将其添加到历史记录中,并返回结果UndoLast() string:从历史记录中移除最后一条命令,调用它的 Undo 方法,并返回结果。如果历史记录为空,则返回Nothing to undo
main.go:通过一系列操作演示命令系统。读取操作数量。对于每个操作,读取操作类型(
insert、delete或undo)。对于插入操作,还要读取要插入的文本。对于删除操作,读取字符数量。通过调用者执行每个操作并打印结果。所有操作完成后,将编辑器的最终内容打印为Final: [content]。
输入格式如下:
- 第 1 行:操作数量
- 每个操作:操作类型,以及按需提供的额外数据(插入操作提供文本,删除操作提供数量)
例如,给定以下输入:
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注意,每条命令都封装了执行和撤销操作所需的一切内容。调用者不需要知道命令具体做什么,只需执行命令并维护历史记录,以支持撤销操作!
亲手试试
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 := &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,并通过调用器执行它
_ = text
_ = editor
case "delete":
// 读取要删除的字符数
countLine, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countLine))
// TODO:创建 DeleteCommand,并通过调用器执行它
_ = count
case "undo":
// TODO:在调用器上调用 UndoLast
}
fmt.Println(result)
}
// 输出最终内容
fmt.Printf("Final: %s\n", editor.Content)
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Go 在线编译器