用 Go 语言实现命令模式:打造智能餐厅点餐系统

命令模式(Command Pattern)是一种强大的行为型设计模式,通过将请求封装为对象,实现客户端与执行者的解耦。本文将以教学风格,结合一个贴近生活的例子——智能餐厅点餐系统,详细讲解命令模式的概念、Go 语言实现、运行逻辑以及优化方向。无论你是设计模式新手还是 Go 语言爱好者,这篇文章都将为你提供清晰且实用的指导。

什么是命令模式?

定义

命令模式将请求封装成一个对象(命令),允许将客户端的请求与执行请求的对象解耦。命令对象包含执行操作所需的信息,客户端通过调用命令触发操作,无需直接与执行者交互。

命令模式包含以下角色:

  • 命令接口(Command):定义执行命令的接口。
  • 具体命令(Concrete Command):实现命令接口,绑定接收者和操作。
  • 接收者(Receiver):实际执行命令的对象。
  • 调用者(Invoker):持有命令对象,触发执行。
  • 客户端(Client):创建命令并交给调用者。

现实类比

想象一个智能餐厅点餐系统:

  • 场景:顾客通过平板点餐,服务员将订单发送到厨房。
  • 角色
    • 命令:点餐请求(如“制作披萨”)。
    • 接收者:厨房的厨师,制作菜品。
    • 调用者:服务员,传递订单。
    • 客户端:顾客,创建订单。
  • 行为:订单被封装为命令,服务员发送到厨房,厨师制作菜品,支持取消订单。

这个场景体现了命令模式的封装性和灵活性。

核心思想

命令模式的核心是封装请求解耦

  • 请求封装成命令对象,包含操作和接收者。
  • 调用者与接收者解耦,调用者只需触发命令。
  • 支持命令的排队、撤销、重做等操作。

用 Go 语言实现命令模式

为什么选择 Go?

Go 语言以简洁和高性能著称,通过接口和结构体可以优雅地实现命令模式。Go 的接口适合定义命令行为,结构体封装具体逻辑。

设计智能餐厅点餐系统

我们将实现一个点餐系统:

  • 接收者:厨房(Kitchen),制作菜品。
  • 命令:点餐命令(OrderPizzaCommand、OrderSaladCommand)。
  • 调用者:服务员(Waiter),执行命令。
  • 客户端:顾客,发起点餐。
  • 行为:顾客点餐,服务Appraisal: 10/10

代码实现

以下是完整的 Go 代码,包含详细注释:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package main

import (
	"fmt"
)

// Command 定义命令接口,包含执行和撤销方法
type Command interface {
	Execute()
	Undo()
}

// Kitchen 是接收者,负责实际的菜品制作
type Kitchen struct{}

// MakePizza 制作披萨
func (k *Kitchen) MakePizza() {
	fmt.Println("厨房:正在制作披萨...")
}

// CancelPizza 取消披萨订单
func (k *Kitchen) CancelPizza() {
	fmt.Println("厨房:已取消披萨订单")
}

// MakeSalad 制作沙拉
func (k *Kitchen) MakeSalad() {
	fmt.Println("厨房:正在制作沙拉...")
}

// CancelSalad 取消沙拉订单
func (k *Kitchen) CancelSalad() {
	fmt.Println("厨房:已取消沙拉订单")
}

// OrderPizzaCommand 是具体命令,封装披萨订单
type OrderPizzaCommand struct {
	kitchen *Kitchen
}

// NewOrderPizzaCommand 创建披萨命令
func NewOrderPizzaCommand(kitchen *Kitchen) *OrderPizzaCommand {
	return &OrderPizzaCommand{kitchen: kitchen}
}

// Execute 实现执行方法
func (c *OrderPizzaCommand) Execute() {
	c.kitchen.MakePizza()
}

// Undo 实现撤销方法
func (c *OrderPizzaCommand) Undo() {
	c.kitchen.CancelPizza()
}

// OrderSaladCommand 是具体命令,封装沙拉订单
type OrderSaladCommand struct {
	kitchen *Kitchen
}

// NewOrderSaladCommand 创建沙拉命令
func NewOrderSaladCommand(kitchen *Kitchen) *OrderSaladCommand {
	return &OrderSaladCommand{kitchen: kitchen}
}

// Execute 实现执行方法
func (c *OrderSaladCommand) Execute() {
	c.kitchen.MakeSalad()
}

// Undo 实现撤销方法
func (c *OrderSaladCommand) Undo() {
	c.kitchen.CancelSalad()
}

// Waiter 是调用者,负责管理并执行命令
type Waiter struct {
	commands []Command // 存储命令列表,支持排队
}

// AddCommand 添加命令到队列
func (w *Waiter) AddCommand(command Command) {
	w.commands = append(w.commands, command)
}

// ExecuteOrders 执行所有命令
func (w *Waiter) ExecuteOrders() {
	fmt.Println("服务员:开始处理订单...")
	for _, command := range w.commands {
		command.Execute()
	}
}

// UndoLastOrder 撤销最后一个命令
func (w *Waiter) UndoLastOrder() {
	if len(w.commands) == 0 {
		fmt.Println("服务员:没有订单可撤销")
		return
	}
	fmt.Println("服务员:撤销最后一个订单...")
	lastCommand := w.commands[len(w.commands)-1]
	lastCommand.Undo()
	w.commands = w.commands[:len(w.commands)-1] // 从队列中移除
}

func main() {
	// 创建接收者(厨房)
	kitchen := &Kitchen{}

	// 创建具体命令
	pizzaCommand := NewOrderPizzaCommand(kitchen)
	saladCommand := NewOrderSaladCommand(kitchen)

	// 创建调用者(服务员)
	waiter := &Waiter{}

	// 模拟顾客点餐
	fmt.Println("顾客:点了一份披萨和一份沙拉")
	waiter.AddCommand(pizzaCommand)
	waiter.AddCommand(saladCommand)

	// 服务员执行订单
	waiter.ExecuteOrders()

	// 模拟顾客取消最后一个订单
	fmt.Println("\n顾客:取消沙拉订单")
	waiter.UndoLastOrder()

	// 再次执行剩余订单
	fmt.Println("\n服务员:处理剩余订单")
	waiter.ExecuteOrders()

	// 尝试撤销空订单
	fmt.Println("\n顾客:再次尝试撤销订单")
	waiter.UndoLastOrder()
}

运行结果

运行代码后,输出如下:

顾客:点了一份披萨和一份沙拉
服务员:开始处理订单...
厨房:正在制作披萨...
厨房:正在制作沙拉...

顾客:取消沙拉订单
服务员:撤销最后一个订单...
厨房:已取消沙拉订单

服务员:处理剩余订单
服务员:开始处理订单...
厨房:正在制作披萨...

顾客:再次尝试撤销订单
服务员:撤销最后一个订单...
厨房:已取消披萨订单

代码解析

  1. 命令接口

    • Command 定义了 ExecuteUndo 方法。
  2. 接收者

    • Kitchen 实现菜品制作和取消逻辑。
  3. 具体命令

    • OrderPizzaCommandOrderSaladCommand 绑定厨房和操作。
  4. 调用者

    • Waiter 维护命令队列,支持执行和撤销。
  5. 主程序

    • 模拟点餐和撤销场景。

命令模式的优缺点

优点

  1. 解耦:客户端与接收者解耦,调用者只需触发命令。
  2. 灵活性:支持命令的存储、排队、撤销、重做。
  3. 扩展性:添加新命令只需实现 Command 接口。
  4. 支持组合:可以组合多个命令形成宏命令。

缺点

  1. 类数量增加:每个命令需一个类,可能导致类激增。
  2. 复杂性:简单场景下可能显得复杂。
  3. 内存开销:存储大量命令可能增加内存使用。

适用场景

  • 需要封装请求,支持撤销或排队。
  • 解耦请求的发送者和接收者。
  • 例如:GUI 系统、事务处理、任务调度。

扩展与优化

支持宏命令

实现宏命令,组合多个命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
type MacroCommand struct {
	commands []Command
}

func (m *MacroCommand) Execute() {
	for _, cmd := range m.commands {
		cmd.Execute()
	}
}

func (m *MacroCommand) Undo() {
	for i := len(m.commands) - 1; i >= 0; i-- {
		m.commands[i].Undo()
	}
}

并发支持

异步执行命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func (w *Waiter) ExecuteOrdersAsync() {
	fmt.Println("服务员:异步处理订单...")
	var wg sync.WaitGroup
	for _, command := range w.commands {
		wg.Add(1)
		go func(cmd Command) {
			defer wg.Done()
			cmd.Execute()
		}(command)
	}
	wg.Wait()
}

命令日志

记录命令历史,支持多次撤销/重做:

1
2
3
4
type Waiter struct {
	commands     []Command // 当前命令队列
	commandHistory []Command // 命令历史
}

总结

本文通过智能餐厅点餐系统,讲解了命令模式的概念和 Go 语言实现。希望这篇文章能帮助你理解命令模式,并在项目中灵活应用。

如何运行代码

  1. 安装 Go 环境(建议 Go 1.18+)。
  2. 保存代码为 command_pattern.go
  3. 运行:
    1
    
    go run command_pattern.go
    

下一步

  • 尝试实现宏命令或命令日志。
  • 在博客留言,分享你的命令模式应用场景!

评论 0