Golang-实现设计模式中的命令模式

内容目录表

本文将解释设计模式中的命令模式,在哪里使用它,以及如何在 Go 中实现它。

顾名思义,命令模式用于创建和执行“命令”。不同的命令有自己的实现,但是执行的步骤是一样的。

命令接口

实现命令模式的基本单元是Command接口:

type Command interface {
	execute()
}

如果命令可以输出错误,接口也可以包含一个错误返回值:

type Command interface {
	execute() error
}

👉 命令接口提供任何其他类型都可以实现的通用签名

让我们看一个例子来说明 Go 中的命令模式。

假设有一家餐厅,它有一定数量的厨师,厨房里有一些菜肴。每个厨师一次可以执行以下一项任务:

  • 做披萨
  • 做沙拉
  • 洗盘子(每次做披萨或沙拉,就会有一个盘子被用掉。洗盘子会重置盘子的总数。)

创建命令

餐厅的三个任务都可以表示为命令。让我们看看如何构建餐厅和三个命令:

// 餐厅包含盘子总数和已清洗的盘子数
type Restaurant struct {
	TotalDishes   int
	CleanedDishes int
}

// `NewRestaurant` 构造一个新的餐厅实例,里面有 10 个盘子,都是干净的
func NewResteraunt() *Restaurant {
	const totalDishes = 10
	return &Restaurant{
		TotalDishes:   totalDishes,
		CleanedDishes: totalDishes,
	}
}

// MakePizzaCommand 是一个结构体,它包含要制作的比萨饼的数量,以及餐厅作为其属性
type MakePizzaCommand struct {
	n          int
	restaurant *Restaurant
}

func (c *MakePizzaCommand) execute() {
	// 减少餐厅的干净盘子数,并在完成后打印一条消息
	c.restaurant.CleanedDishes -= c.n
	fmt.Println("made", c.n, "pizzas")
}

// MakeSaladCommand 类似于 MakePizza 命令
type MakeSaladCommand struct {
	n          int
	restaurant *Restaurant
}

func (c *MakeSaladCommand) execute() {
	c.restaurant.CleanedDishes -= c.n
	fmt.Println("made", c.n, "salads")
}

type CleanDishesCommand struct {
	restaurant *Restaurant
}

func (c *CleanDishesCommand) execute() {
	// 将已清洗的盘子数重置为盘子总数,并在完成后打印一条消息
	c.restaurant.CleanedDishes = c.restaurant.TotalDishes
	fmt.Println("dishes cleaned")
}

👉 MakePizzaCommandMakeSaladCommandCleanDishesCommand 都实现了 Command 接口的 execute 方法。

我们现在可以向 Restaurant 添加方法以创建这些命令的实例:

func (r *Restaurant) MakePizza(n int) Command {
	return &MakePizzaCommand{
		restaurant: r,
		n:          n,
	}
}

func (r *Restaurant) MakeSalad(n int) Command {
	return &MakeSaladCommand{
		restaurant: r,
		n:          n,
	}
}

func (r *Restaurant) CleanDishes() Command {
	return &CleanDishesCommand{
		restaurant: r,
	}
}

通过这种方式,Restaurant 充当了一种命令工厂。

执行命令

创建命令后,可以通过调用 execute 方法来执行它。虽然这看起来没什么特别之处,但是当我们需要执行多个不同的命令时,它的优势就体现出来了。

为了证明这一点,让我们为我们的餐厅添加一些厨师:

// 厨师将他们的命令列表作为属性
type Cook struct {
	Commands []Command
}

// executeCommands 方法将所有命令一一执行
func (c *Cook) executeCommands() {
	for _, c := range c.Commands {
		c.execute()
	}
}

Cook 是我们餐厅的执行者,接受命令并一个接一个地执行。

👉 让 Cook 获取一组 Command 对象,将它们与命令的实际执行分开,因为厨师不需要知道每个命令的内部实现。

开始营业

到目前为止,我们的示例中有三个实体:

  1. 餐厅,所有命令在其上执行
  2. 厨师,执行命令
  3. 命令本身

使用这三个实体,我们可以为每个厨师构建一个作业队列,以在餐厅执行他们各自的命令:

func main() {
	// 初始化一个新的餐厅
	r := NewResteraunt()

	// 创建要执行的任务列表
	tasks := []Command{
		r.MakePizza(2),
		r.MakeSalad(1),
		r.MakePizza(3),
		r.CleanDishes(),
		r.MakePizza(4),
		r.CleanDishes(),
	}

	// 创建将执行任务的厨师
	cooks := []*Cook{
		&Cook{},
		&Cook{},
	}

	// 将任务交替的分配给在现有的厨师
	for i, task := range tasks {
		// 我们使用当前任务索引的模数来给厨师交替分配任务
		cook := cooks[i%len(cooks)]
		cook.Commands = append(cook.Commands, task)
	}

	// 现在所有的厨师都有他们的命令,我们可以调用 `executeCommands` 方法让每个厨师执行他们各自的命令
	for i, c := range cooks {
		fmt.Println("cook", i, ":")
		c.executeCommands()
	}
}

您可以在此处运行完整示例,将输出:

cook 0 :
made 2 pizzas
made 3 pizzas
made 4 pizzas
cook 1 :
made 1 salads
dishes cleaned
dishes cleaned

何时使用命令模式

当你需要执行任务,但又想将任务管理与任务本身的执行分开时,命令模式很有用。

在本文中的示例中,通过将每个任务封装在一个公共接口中,将执行者(厨师)与任务分开。


也可以看看


全国大流量卡免费领

19元月租ㆍ超值优惠ㆍ长期套餐ㆍ免费包邮ㆍ官方正品