2023 跟我一起学设计模式:命令行模式

什么是命令行模式

命令模式是一种数据驱动的行为设计模式,首先请求以命令的形式包裹在对象中并传递给调用对象,然后调用对象可以查找处理该命令的合适对象,并且将该命令传递给相应的对象,最后由该对象执行命令。命令行模式可以将请求或者简单的操作转换为对象,此类转换可以让开发者能够延迟执行或者远程执行请求,还可以将其放入队列中。

image.png

命令模式结构

  1. 调用者 (Invoker)调用者需要有个参数来保存命令类对象。 发送者执行命令, 而不是向接收者直接发送需要执行的命令请求。 但是发送者并不负责创建命令对象: 它通常会通过构造函数从客户端处获得预先生成的命令。
  2. 命令 : 命令接口通常只声明一个执行命令的方法。
  3. 具体命令 :实现各种类型的请求的类。 具体命令自身并不执行任务, 而是会将调用委派给一个业务逻辑对象。 但为了简化代码,可以将具体命令合并。
  4. 接收者 :包含部分业务逻辑的接口和类。 大部分对象都可以作为接收者。 绝大部分命令只处理将请求传递到接收者, 接收者会完成具体的工作。
  5. 客户端 : 创建并配置具体命令对象。 客户端必须将包括接收者对象在内的所有请求参数传递给命令的初始化函数。 此后生成的命令就可以与一个或多个调用者相关联了。

命令行模式使用场景

  • 如果你需要通过操作参数化对象可使用命令模式。

命令模式可将特定的方法调用转化为独立对象的调用。 这种模式给代码带来了很好的灵活性: 我们将命令类作为方法的参数进行传递给接受者,将命令类保存在其他对象中, 或者在程序运行时切换已连接好的命令等操作。

例如: 你正在开发一个前端界面组件 (比如后台管理界面的左侧菜单树), 你希望用户能够配置菜单项, 并且在用户点击菜单项时触发对应的操作动作命令。

  • 如果开发者要将操作放入队列中或者执行操作,可使用命令模式。

同其他对象一样, 可以将命令实现序列化 (转化为字符串), 从而能方便地写入文件或数据库中存储。 在需要用到的时候, 该字符串可被恢复成为最初的命令对象。 因此开发者可以延迟执行命令或者计划什么时候执行命令。 但其功能远不止如此! 使用同样的方式, 开发者还可以将命令放入队列、记录命令或者通过网络发送命令给其他系统调用。

  • 如果开发者需要实现操作回滚的功能, 可以使用命令行模式。

为了能够回滚操作, 开发者需要实现已执行操作的历史记录功能。 命令历史记录功能是一种包含所有已执行命令对象及其相关程序状态备份的栈结构。这种方法有两个缺点,一个是程序状态的保持功能比较难实现, 因为部分状态可能是私有的。 开发者可以使用备忘录模式来在一定程度上解决这个问题。 其次, 备份状态可能会占用大量物理机内存。 因此, 我们有时需要借助另一种实现方式: 命令无需恢复原始状态, 而是执行反向操作。 反向操作也有代价: 它可能会很难甚至是无法实现。

命令行模式的实现方式

  1. 定义仅有一个执行方法的命令接口。

  2. 定义具体命令类以及其方法,抽取请求并使其成为实现命令接口的具体命令类。每个具体的命令类需要有一组成员变量,用于保持其请求参数,和实际接受者对象的应用,这些变量都需要通过命令进行初始化。

  3. 定义调用者类及其方法。找到担任调用者职责的类,在这些类中添加存储命令的成员变量。调用者只能通过命令接口与其命令进行交互,调用者通常并不创建命令对象,他会通过客户端来获取命令对象。

  4. 定义接受者类机器方法。

  5. 创建客户端,客户端必须按照以下顺序来初始化对象:

    • 创建接收者对象。
    • 创建命令, 如果有需要可以将其关联至接收者对象类。
    • 创建发送者类并将其与特定命令类关联起来。

命令模式优点

  • 命令行模式可以减低代码的耦合度,并且将请求调用者与请求接受者进行了解耦
  • 命令行模式的扩展性高。在命令行模式中如果要扩展新的命令,那么直接定义新的命令即可, 如果要执行一组命令,那么给接受者发送一组命令即可

命令模式缺点

  • 命令行模式会增加复杂度。扩展命令会增加类数量的大量增加,从而增加了系统实现的复杂度。
  • 命令模式需要针对每个命令都开发一个与之对应的命令类,从而增加了较多的代码量

代码示例

下面我们通过收音机的例子来加深对命令模式的了解。 在我们的生活中,你可以通过以下方式方式来打开收音机:

  • 按下遥控器上的 ON 开关;
  • 按下收音机上的 ON 开关。

首先实现ON命令对象,并且将收音机作为接受者; 当在此命令上调用 execute执行方法时,该方法会调用 Radio.On打开收音机函数,用于打开收音机。最后定义请求者的工作: 遥控器和收音机。 二者都嵌入 ON 命令对象。

创建独立命令对象的优势在于可以将操作逻辑与底层业务逻辑解耦,这样就可以不用为每个请求者开发不同的处理这了。命令对象中包含了执行操作所需要的全部信息,所以可以延迟执行操作。具体的操作如下:

定义调用者类: Button 以及其方法 Press()

button.go: 请求者

go





复制代码






package main





type Button struct {
	command Command
}




func (b *Button) press() {
	b.command.execute()
}

定义仅有一个方法的接口 Command

command.go: 命令接口

go





复制代码










package main






type Command interface {
	execute()
}

定义具体命令OnCommand 以及其方法 Execute()

onCommand.go:

go





复制代码






package main





type OnCommand struct {
	device Device
}




func (c *OnCommand) execute() {
	c.device.On()
}

offCommand.go: 具体接口

go





复制代码










package main






type OffCommand struct {
	device Device
}


func (c *OffCommand) execute() {
	c.device.Off()
}

定义接受者接口 Device

device.go: 接收者接口

go





复制代码










package main






type Device interface {
	On()
	Off()
}

定义接受者类Radio 以及其对应的方法 On(), Off()

tv.go: 具体接收者

go





复制代码






package main





import "fmt"

type Radio struct {
	isRunning bool
}


func (r *Radio) On() {
	r.isRunning = true
	fmt.Println("Turning Radio on")
}

func (r *Radio) Off() {
	r.isRunning = false
	fmt.Println("Turning Radio off")
}

main.go: 客户端代码

go
复制代码










package main




func main() {
	radio := &Radio{}



	onCommand := &OnCommand{
		device: radio,
	}

	offCommand := &OffCommand{device: radio}

	onButton := &Button{
		command: onCommand,
	}
	onButton.press()

	offButton := &Button{
		command: offCommand,
	}

	offButton.press()
}

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYWnFyyO' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片