惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

腾讯CDC
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - Franky
博客园_首页
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
IT之家
IT之家
The Cloudflare Blog
V
Visual Studio Blog
罗磊的独立博客
T
Tailwind CSS Blog
S
SegmentFault 最新的问题
Hugging Face - Blog
Hugging Face - Blog
V
V2EX
阮一峰的网络日志
阮一峰的网络日志
D
Docker
Last Week in AI
Last Week in AI
B
Blog RSS Feed
C
Check Point Blog
J
Java Code Geeks
The GitHub Blog
The GitHub Blog
有赞技术团队
有赞技术团队
博客园 - 聂微东
MongoDB | Blog
MongoDB | Blog
雷峰网
雷峰网

博客园 - 立志做一个好的程序员

【转】Golang使用时区时候报错 详解 Go 中的 rune 类型 【转】扫盲:Windows桌面应用开发框架:原生、跨平台、云桌面 Go-Zero自定义goctl实战:定制化模板,加速你的微服务开发效率 Go单体服务开发最佳实践 【转】Androidstudio报错Algorithm HmacPBESHA256 not available 借助ai来分析代码,理解代码 【转】linux设置密钥登录(只允许密钥登录) 【转】QPS和并发数的关系 [转]Golang Functional Options Pattern 【转】Raft 理论基础 【转】spin lock 和mutex [转]Go sync.Once:简约而不简单的并发利器 [转]Golang atomic.CompareAndSwapInt64()实例讲解 [转]手把手教你如何用golang实现一个timewheel时间轮 [转]Golang线程池实现百万级高并发 [转]Go网络编程 · 一条TCP连接讲透九大知识点 【转】,接上面3篇.Implement Sql Database Driver in 100 Lines of Go 【转】dive into golang database/sql(3)
【转】Command Pattern in Go (Golang)
立志做一个好的程序员 · 2024-03-12 · via 博客园 - 立志做一个好的程序员

原文: https://www.sohamkamani.com/golang/command-pattern/

package main

import "fmt"

// The restaurant contains the total dishes and the total cleaned dishes
type Restaurant struct {
	TotalDishes   int
	CleanedDishes int
}

// `NewRestaurant` constructs a new restaurant instance with 10 dishes,
// all of them being clean
func NewResteraunt() *Restaurant {
	const totalDishes = 10
	return &Restaurant{
		TotalDishes:   totalDishes,
		CleanedDishes: totalDishes,
	}
}

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,
	}
}

type Command interface {
	execute()
}

// The MakePizzaCommand is a struct which contains
// the number of pizzas to make, as well as the
// restaurant as its attributes
type MakePizzaCommand struct {
	n          int
	restaurant *Restaurant
}

func (c *MakePizzaCommand) execute() {
	// Reduce the total clean dishes of the restaurant
	// and print a message once done
	c.restaurant.CleanedDishes -= c.n
	fmt.Println("made", c.n, "pizzas")
}

// The MakeSaladCommand is similar to the MakePizza command
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() {
	// Reset the cleaned dishes to the total dishes
	// present, and print a message once done
	c.restaurant.CleanedDishes = c.restaurant.TotalDishes
	fmt.Println("dishes cleaned")
}

// A Cook comes with their list of commands as attributes
type Cook struct {
	Commands []Command
}

// The executeCommands method executes all the commands
// one by one
func (c *Cook) executeCommands() {
	for _, c := range c.Commands {
		c.execute()
	}
}

func main() {
	// initialize a new resaurant
	r := NewResteraunt()

	// create the list of tasks to be executed
	tasks := []Command{
		r.MakePizza(2),
		r.MakeSalad(1),
		r.MakePizza(3),
		r.CleanDishes(),
		r.MakePizza(4),
		r.CleanDishes(),
	}

	// create the cooks that will execute the tasks
	cooks := []*Cook{
		&Cook{},
		&Cook{},
	}

	// Assign tasks to cooks alternating between the existing
	// cooks.
	for i, task := range tasks {
		// Using the modulus of the current task index, we can
		// alternate between different cooks
		cook := cooks[i%len(cooks)]
		cook.Commands = append(cook.Commands, task)
	}

	// Now that all the cooks have their commands, we can call
	// the `executeCommands` method that will have each cook
	// execute their respective commands
	for i, c := range cooks {
		fmt.Println("cook", i, ":")
		c.executeCommands()
	}
}

--------------------

Command Pattern in Go (Golang)

This article will explain the command pattern, where to use it, and how to implement it in Go.

The command pattern, as the name suggests, is used when we want to create and execute “commands”. Different commands have their own implementation, but have the same steps for execution.

The Command Interface

The basic unit for implementing the command pattern is the Command interface:

type Command interface {
	execute()
}

If the command can error out, the interface can contain an error return value as well:

type Command interface {
	execute() error
}

👉 The command interface provides a generic signature which any other type can implement

Let’s look at an example to illustrate the command pattern in Go.

Consider a restaurant, which has a certain number of cooks, and dishes in the kitchen. Each cook can perform one of the following tasks at a time:

  • Cook pizza
  • Make salad
  • Wash dishes Every time a pizza or salad is made, a dish is used up. Washing the dishes resets the total number of dishes.

illustration showing restaurant with cooks and dishes

Creating Commands

The three tasks for the restaurant can each be represented as commands. Let’s see how we can construct the restaurant and the three commands:

// The restaurant contains the total dishes and the total cleaned dishes
type Restaurant struct {
	TotalDishes   int
	CleanedDishes int
}

// `NewRestaurant` constructs a new restaurant instance with 10 dishes,
// all of them being clean
func NewResteraunt() *Restaurant {
	const totalDishes = 10
	return &Restaurant{
		TotalDishes:   totalDishes,
		CleanedDishes: totalDishes,
	}
}

// The MakePizzaCommand is a struct which contains
// the number of pizzas to make, as well as the
// restaurant as its attributes
type MakePizzaCommand struct {
	n          int
	restaurant *Restaurant
}

func (c *MakePizzaCommand) execute() {
	// Reduce the total clean dishes of the restaurant
	// and print a message once done
	c.restaurant.CleanedDishes -= c.n
	fmt.Println("made", c.n, "pizzas")
}

// The MakeSaladCommand is similar to the MakePizza command
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() {
	// Reset the cleaned dishes to the total dishes
	// present, and print a message once done
	c.restaurant.CleanedDishes = c.restaurant.TotalDishes
	fmt.Println("dishes cleaned")
}

👉 MakePizzaCommandMakeSaladCommand, and CleanDishesCommand all implement the Command interface with their execute method.

We can now add methods to the Restaurant in order to create instances of these commands:

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,
	}
}

In this way, the Restaurant acts as a kind of factory for commands.

Executing Commands

Once a command is created, it can be executed by calling the execute method. Although this may seem simple, it has great value when we need to execute multiple different commands.

To demonstrate this, let’s add in some cooks for our restaurant:

// A Cook comes with their list of commands as attributes
type Cook struct {
	Commands []Command
}

// The executeCommands method executes all the commands
// one by one
func (c *Cook) executeCommands() {
	for _, c := range c.Commands {
		c.execute()
	}
}

The Cook is the executor of our restaurant, accepting commands and executing them one after the other.

👉 Having the Cook take a set of Command object separates them from the actual execution of the commands, since cooks don’t need to know the internal implementation of each command

Putting It All Together

So far, we have three entities in our example:

  1. The Restaurant, on which the commands execute
  2. The Cooks, which execute the commands
  3. The Commands themselves

Using these three entities, we can construct a job queue for each cook to execute their respective command on the restaurant:

func main() {
	// initialize a new resaurant
	r := NewResteraunt()

	// create the list of tasks to be executed
	tasks := []Command{
		r.MakePizza(2),
		r.MakeSalad(1),
		r.MakePizza(3),
		r.CleanDishes(),
		r.MakePizza(4),
		r.CleanDishes(),
	}

	// create the cooks that will execute the tasks
	cooks := []*Cook{
		&Cook{},
		&Cook{},
	}

	// Assign tasks to cooks alternating between the existing
	// cooks.
	for i, task := range tasks {
		// Using the modulus of the current task index, we can
		// alternate between different cooks
		cook := cooks[i%len(cooks)]
		cook.Commands = append(cook.Commands, task)
	}

	// Now that all the cooks have their commands, we can call
	// the `executeCommands` method that will have each cook
	// execute their respective commands
	for i, c := range cooks {
		fmt.Println("cook", i, ":")
		c.executeCommands()
	}
}

You can run the complete example here

This will give you the output:

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

When to Use Command Pattern

The command pattern is useful when you need to execute tasks, but you want to separate the tasks management from the execution of the task itself.

In the example illustrated in this post, we separated the executors (the cooks) from the tasks by encapsulating each task in a common interface.

Can you think of any other cases where command patterns would be useful? Let me know in the comments!

Here are some other Golang design patterns that I’ve covered: