位置:首页 > Ruby > Ruby设计模式编程中命令模式的使用解析

Ruby设计模式编程中命令模式的使用解析

时间:2026-08-17  |  作者:多维游侠  |  阅读:0

在对象行为型模式里,命令模式的使用率一直很高,别名也挺多——Action、Transaction,本质都是一回事。

意图

命令模式的核心意图很清晰:把请求本身封装成对象。

这样一来,你就可以对不同请求进行参数化、排队、记录日志,甚至支持取消操作。

这里所说的“不同请求”,其实就意味着变化。

也就是说,请求本身是一个可能扩展的功能点。

动机

动机也很直白:方便扩展。

不想因为增加一个新功能,就把原有的代码翻个底朝天。

结构

201646111037926.png (396×266)

协作说明

参与的角色各司其职:

  • Command:声明一个接口,用来实现某个操作。
  • ConcreteCommand:把动作与 Receiver 对外绑定,通过调用 Receiver 对象的相应方法来实现 Command 的方法。
  • Client:创建 ConcreteCommand 对象,并指定它的 Receiver。
  • Invoker:要求这个 Command 去实现请求。
  • Receiver:真正知道如何处理具体请求的类。

整个流程是这样的:

  • 客户端先创建一个具体的 Command 对象,并指定好接收者。
  • 调用者把 Command 对象存下来。
  • 然后通过执行它的 Execute 方法来实现请求。
  • 如果命令支持撤销,具体对象在执行前会先把相关状态存起来。
  • 最后,具体的 Command 对象通过调用接收者的方法,完成相应的请求。

适用性

什么时候该用命令模式?

  • 类似 MenuItem 的场景,需要把待执行的动作抽象出来,参数化某个对象。
  • 需要在不同时刻指定、排列和执行请求。
  • 需要支持撤销操作。
  • 需要支持修改日志。
  • 在基于原语操作之上构建一个高层操作系统——说白了就是事务。

说到动态性,Ruby 中的 block 其实就是一种命令模式。

效果

命令模式把调用者与接收者解耦。换句话说,就是调用与实现分开。

调用者想实现功能,只需要调用 Command 接口的 Execute 方法就行。

具体的 Commands 对象是第一层对象,它们可以像其他对象一样被扩展或操作。

你可以把多个 Commands 对象组合成一个组合命令。

这其实是组合模式的一个实例,命令排队也是它的特殊情况。

更重要的是,你可以轻松地添加新的命令,完全不需要修改现有代码。

这一点正好符合开闭原则——对修改关闭,对扩展开放。

实现时需要考虑的问题

有两个问题值得琢磨:

  • 命令对象应该达到什么智能程度?
  • 如何支持撤销和重做?

误用

别把它想得太玄乎。

命令模式不是说“做这个”,而是说“记住这个如何做”,然后稍后再“按照我刚才要你记的方法做这个”。

另外,撤销操作要格外小心。

很多操作是破坏性的,比如删除文件。一旦撤了,文件可能就找不回来了。

类图

201646111142669.jpg (574×253)

Ruby 代码示例

来看一段 Ruby 代码示例:

class Button
 
 attr_accessor :name, :command
 
 def initialize name, command
  @name = name
  @command = command
 end
 
 def do_something
  @command.execute
 end
 
end

class Command
 
 def execute
  "root execute"
 end
 
end

class PaintCommand < Command
 
 def execute
  "draw something"
 end
 
end

class VocalCommand < Command
 
 def execute
  "talk something"
 end
 
end

paintCommand = PaintCommand.new
vocalCommand = VocalCommand.new
button = Button.new("button", paintCommand)
p button.do_something
button.command = vocalCommand
p button.do_something

这里定义了一个主体类 Button,它聚合了一个命令对象 Command。

同时声明了三个具有继承关系的命令类:Command、PaintCommand 和 VocalCommand。

系统里可能有很多 Button,每个 Button 要干的事都不一样。

也就是说,方法 do_something 里的代码是不确定的。

所以,把这部分代码分离到单独的对象里管理。

这个对象就叫命令对象。

命令对象只负责它该完成的任务或指令。

主体对象则可以根据需要,在任何时候调用要执行的命令。

从调用代码也能看出,切换当前 Button 的命令实现非常方便灵活,只需要调用 set 方法就行。

如果采用 Button 继承的方式,第一会造成类爆炸,第二在切换命令实现时,比这种方式要麻烦得多。

使用 Proc 实现命令模式

用 Ruby 的 Proc 来实现命令模式呢?更简洁:

class Button
 
 attr_accessor :name
 
 def initialize name, &command
  @name = name
 end
 
 def do_something &command
  command.call
 end
 
end

paint_command = lambda do
 p "paint something"
end

vocal_command = lambda do
  p "talk something"
end

button = Button.new ("name")
button.do_something &vocal_command
button.do_something &paint_command

用 block 代替命令类,更简单、更易懂。

实际项目里用 Proc 还是用命令类,看情况来。

  • 如果命令对象很复杂,有自己的状态和方法,那就选命令类。
  • 如果只是处理点小事情,用 Proc 就挺好。

如果需要执行的命令很多,可以定义命令队列。

也就是一个命令里面管理多个命令,调用时挨个执行。

这一点和组合模式很像。

命令模式与观察者模式的区别

从某种角度看,观察者模式和命令模式有点相似。

它们都是把具有共同特征的对象聚合到自己类里,再根据情况调用。

但两者有明显的区别:用途不同

  • 观察者模式是为了让被观察者把变化通知给各个观察者。
  • 而命令模式不关心是否要通知别的命令,命令对象只负责执行自己的任务或指令。

另外,命令模式可以记住前一次操作。

所以很多文本编辑器的撤销/重做功能,用的就是命令模式。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多