命令模式實現撤銷與恢復
命令模式定義
將請求封裝成對象,以便使用不同的請求、隊列或日志來參數化其他對象。
命令對象可以把行動及參數封裝起來,於是這些行動可以被:
- 重復多次
- 取消
- 恢復(取消后又再)
整個模式的類圖如下:
通過 ICommand 接口,實現了控制類與調用者的解耦。
下面通過一個簡單的實例來詳細說明這種解耦以恢復撤銷是如何實現。
假定有一個風扇,當前有四個按鈕,分別是 高速模式 , 低速模式 , 撤銷 ,恢復。
風扇類類如下(對應類圖中的具體類 ConcreteClass):
有高速運轉、低速運轉等方法
public class CeilingFan
{
public const int HIGH = 2;
public const int LOW = 1;
public const int OFF = 0;
int speed;
public CeilingFan() { speed = OFF; }
public void High() { speed = HIGH; }
public void Low() { speed = LOW; }
public int getSpeed() { return speed; }
}
命令接口
public interface ICommand
{
void execute();
void undo();
}
風扇命令類 (Concrete)
// 高速運行類
public class CeilingFanHighCommand : ICommand
{
CeilingFan ceilingFan; // 類中不用 new 方法創建類,降低耦合
int preSpeed; // 記錄執行按鍵前的狀態,便於回測
public CeilingFanHighCommand(CeilingFan cf)
{
ceilingFan = cf;
}
public void execute()
{
preSpeed = ceilingFan.getSpeed();
ceilingFan.High();
}
public void undo()
{
switch(preSpeed)
{
case CeilingFan.HIGH:
ceilingFan.High();
break;
case CeilingFan.LOW:
ceilingFan.Low();
break;
default:
ceilingFan.Off();
break;
}
}
}
// 低速運行類
public class CeilingFanLowCommand : ICommand
{
CeilingFan ceilingFan; // 類中不用 new 方法創建類,降低耦合
int preSpeed; // 記錄執行按鍵前的狀態,便於回測
public CeilingFanHighCommand(CeilingFan cf)
{
ceilingFan = cf;
}
public void execute()
{
preSpeed = ceilingFan.getSpeed();
ceilingFan.Low();
}
public void undo()
{
switch(preSpeed)
{
case CeilingFan.HIGH:
ceilingFan.High();
break;
case CeilingFan.LOW:
ceilingFan.Low();
break;
default:
ceilingFan.Off();
break;
}
}
}
// 關閉類
public class CeilingFanLowCommand : ICommand
{
CeilingFan ceilingFan;
int preSpeed;
public CeilingFanHighCommand(CeilingFan cf)
{
ceilingFan = cf;
}
public void execute()
{
preSpeed = ceilingFan.getSpeed();
ceilingFan.Off();
}
public void undo()
{
switch(preSpeed)
{
case CeilingFan.HIGH:
ceilingFan.High();
break;
case CeilingFan.LOW:
ceilingFan.Low();
break;
default:
ceilingFan.Off();
break;
}
}
}
以上風扇的相關命令構建后,需要被一個類來調用控制它,這個控制類不僅僅可以控制風扇,同時可以控制電燈,冰箱等等,所以不能與風扇耦合。
控制類 (對應類圖中 Control)
public class Control
{
List<ICommand> onCommands;
Stack<ICommand> undoCommands;
Stack<ICommand> redoCommands; // 記錄前一個命令, 便於 undo
public Control()
{
onCommands = new List<ICommand>();
undoCommands = new Stack<ICommand>();
redoCommands = new Stack<ICommand>();
}
public void SetCommand(int slot, ICommand onCmd)
{
onCommands[slot] = onCmd;
}
public void OnButtonWasPressed(int slot)
{
if (onCommands[slot] != null)
{
onCommands[slot].execute();
undoCommands.Push(onCommands[slot]);
}
}
public void UndoButtonWasPressed() // 撤銷,此處用 stack 后進先出的特性
{
if (undoCommands.Count > 0)
{
ICommand cmd = undoCommands.Pop();
redoCommands.Push(cmd);
cmd.undo();
}
}
public void RedoButtonWasPressed()
{
if(redoCommands.Count > 0)
{
ICommand cmd = redoCommands.Pop();
undoCommands.Push(cmd);
cmd.execute();
}
}
}
以上一個命令模式大體上完成了。
下面讓客戶進行調用測試
// 測試類 (類途中的 RemoteLoader)
class Program
{
static void Main(string[] args)
{
CeilingFan cf = new CeilingFan();
CeilingFanHighCommand cfh = new CeilingFanHighCommand(cf);
CeilingFanLowCommand cfl = new CeilingFanLowCommand(cf);
Control ctr = new Control();
// 假設 button0, button1 分別為高速低速
ctr.SetCommand(0, cfh);
ctr.SetCommand(1, cfl);
ctr.OnButtonWasPressed(0);
ctr.OnButtonWasPressed(1);
ctr.UndoButtonWasPressed();
ctr.RedoButtonWasPressed();
}
}
輸出內容如下:
turn ceilfan to high speed!
turn ceifan to low speed!
turn ceilfan to high speed!
turn ceifan to low speed!