★★★★☆
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
结构图
角色
- 命令者(Command)角色:为所有具体命令类声明抽象接口。此类应声明为抽象。
- 具体命令者(ConcreteCommand)角色:。定义一个接收者和行为之间的弱耦合;实现执行方法(Execute),负责调用接受者的相应操作。
- 请求者(Invoker)角色:负责调用命令者执行请求。
- 接收者(Receiver)角色:负责具体实施和执行一个请求。
动机
在软件构建过程中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合--比如需要对行为进行“记录、撤销/重做(Undo/Redo)、事务”等处理,这种无法抵御变化的紧耦合是不合适的。
在这种情况下,如何将“行为请求者”与“行为实现解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。
意图
将一个对象请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录日志,以及支持可撤销的操作。
示意性代码
示意性代码
Public Class MainAppClass MainApp
'MainApp test application
Public Shared Sub Main()Sub Main()
'Create receiver, command, and invoker
Dim receiver As New Receiver
Dim command As Command = New ConcreteCommand(receiver)
Dim invoker As New Invoker
'Set and execute command
invoker.SetCommand(command)
invoker.ExecuteCommand()
'Wait for user
Console.ReadLine()
End Sub
End Class
'"Command"
Public MustInherit Class CommandClass Command
Protected receiver As Receiver
'Constructor
Public Sub New()Sub New(ByVal receiver As Receiver)
Me.receiver = receiver
End Sub
Public MustOverride Sub Execute()Sub Execute()
End Class
'"ConcreteCommand"
Public Class ConcreteCommandClass ConcreteCommand
Inherits Command
'Constructor
Public Sub New()Sub New(ByVal receiver As Receiver)
MyBase.new(receiver)
End Sub
Public Overrides Sub Execute()Sub Execute()
Receiver.Action()
End Sub
End Class
'"Receiver"
Public Class ReceiverClass Receiver
Public Sub Action()Sub Action()
Console.WriteLine("Called Receiver.Action()")
End Sub
End Class
'"Invoker"
Public Class InvokerClass Invoker
Private command As Command
Public Sub SetCommand()Sub SetCommand(ByVal command As Command)
Me.command = command
End Sub
Public Sub ExecuteCommand()Sub ExecuteCommand()
command.Execute()
End Sub
End Class
一个实例
下面的命令模式代码演示了对一个简单的计算器实现重做和撤销操作。
实例代码
'Command pattern -- Real World example
Public Class MainAppClass MainApp
'MainApp test application
Public Shared Sub Main()Sub Main()
'Create user and let her compute
Dim user As New User
user.Compute("+"c, 100)
user.Compute("-"c, 50)
user.Compute("*"c, 10)
user.Compute("/"c, 2)
'Undo 4 commands
user.Undo(4)
'Redo 3 commands
user.Redo(3)
'Wait for user
Console.ReadLine()
End Sub
End Class
'"Command"
Public MustInherit Class CommandClass Command
Public MustOverride Sub Excute()Sub Excute()
Public MustOverride Sub UnExcute()Sub UnExcute()
End Class
'"ConcreteCommand"
Public Class CalculatorCommandClass CalculatorCommand
Inherits Command
Private _operand As Integer
Private _operator As Char
Private _calculator As Calculator
Public Sub New()Sub New(ByVal calculator As Calculator, ByVal [operator] As Char, ByVal operand As Integer)
Me._operator = [operator]
Me._operand = operand
Me._calculator = calculator
End Sub
'Property
Public ReadOnly Property Operand()Property Operand() As Integer
Get
Return _operand
End Get
End Property
Public ReadOnly Property [()Property [Operator]() As Char
Get
Return _operator
End Get
End Property
Public Overrides Sub Excute()Sub Excute()
_calculator.Operation(_operator, Operand)
End Sub
Public Overrides Sub UnExcute()Sub UnExcute()
_calculator.Operation(Undo(_operator), _operand)
End Sub
Public Function Undo()Function Undo(ByVal [operator] As Char) As Char
Select Case [operator]
Case "+"c
Return "-"c
Case "-"c
Return "+"c
Case "*"c
Return "/"c
Case "/"c
Return "*"c
Case Else
Return " "c
End Select
End Function
End Class
'"Receiver"
Public Class CalculatorClass Calculator
Private cur As Integer = 0
Public Sub Operation()Sub Operation(ByVal [operator] As Char, ByVal operand As Integer)
Select Case [operator]
Case "+"c
cur += operand
Case "-"c
cur -= operand
Case "*"c
cur *= operand
Case "/"c
cur \= operand
End Select
Console.WriteLine( _
"Current value = {0,3} (following {1} {2})", _
cur, [operator], operand)
End Sub
End Class
'"Invoker"
Public Class UserClass User
'Initializers
Private commands As New ArrayList
Private calculator As Calculator
Private current As Integer = 0
Public Sub Undo()Sub Undo(ByVal levels As Integer)
'Perform redo operations
Console.WriteLine()
Console.WriteLine("---- Undo {0} levels ", levels)
For i As Integer = 1 To levels
If current > 0 Then
Dim command As Command
current -= 1
command = TryCast(commands(current), Command)
command.UnExcute()
End If
Next
End Sub
Public Sub Redo()Sub Redo(ByVal levels As Integer)
'Perform undo operations
Console.WriteLine()
Console.WriteLine("---- Redo {0} levels ", levels)
For i As Integer = 1 To levels
If current < commands.Count - 1 Then
Dim command As Command
current += 1
command = TryCast(commands(current), Command)
command.UnExcute()
End If
Next
End Sub
'Compute
Public Sub Compute()Sub Compute(ByVal [operator] As Char, ByVal operand As Integer)
'Create command operation and execute it
Dim command As Command = New CalculatorCommand(calculator, [operator], operand)
command.Excute()
'Add command to undo list
commands.Add(command)
current += 1
End Sub
End Class
Command模式的几个要点:
1、Command模式的根本目的在于将“行为请求者”与“行为实现者”解耦,在面向对象语言中,常见的实现手段是“将行为抽象为对象”。
2、实现Command接口的具体对象ConcreteCommand有时候根据需要可能会保存一些额外的状态信息。
3、通过使用Composite模式,可以将多个“命令”封装为一个“复合命令”(MacroCommand)
4、Command模式与VB.Net中的Delegate有些类似。但两者定义行为接口的规范有所区别:Command以面向对象中的“接口-实现”来定义行为接口规范,更严格,更符合抽象原则;Delegate以函数签名来定义行为接口规范,更灵活,但抽象力比较弱。
我的理解
封装对象责任,支持责任的变化。
参考资料
《C#设计模式(15)-Command Pattern》 吕震宇
《C#面向对象设计模式纵横谈系列课程(15)》 李建中老师