重溫《深入淺出設計模式》命令模式 (Book Review of Head First Design Pattern, Command Pattern)
2020-07-02
命令模式、外觀模式,乍看之下有些相似,但從模式的意義上的分別兩者就可以感受到不同。命令模式是將命令(要求、需求)封裝成物件,並且讓接受命令者與命令的執行職責拆分,類似餐廳點餐從老闆邊接受客人點餐、邊準備料理分為餐廳前台與餐廳後台,同時命令模式可以將命令與執行時間分隔,延後或者指定執行命令的時間;外觀模式則是將繁雜的類別方法,透過一個簡化的控制類別封裝複雜的演算邏輯,對使用者而言僅需要一個方法就能完成所有關聯的步驟。
命令模式
Encapsulate a request as an object, thereby letting you parametrize clients with different requests, queue or log requests, and support undoable operations.
After Pattern
Command 為 Interface 表示將命令的行為封裝為類別,並由衍生類別去實作 Execute。
Command.cs
NoCommand.cs
public interface ICommand
{
void Execute();
}
internal class NoCommand : ICommand
{
public void Execute()
{
throw new System.NotImplementedException();
}
}
CeilingFan 為 Receiver 實際的演算邏輯負責者;CeilingFanOffCommand, CeilingFanOnCommand 為實作 ICommand 的 Concrete Command 用來將命令的行為封裝,並且會擁有 Receiver 參考,用以呼叫 Receiver 的方向。
CeilingFan.cs
CeilingFanOffCommand.cs
CeilingFanOnCommand.cs
internal class CeilingFan
{
private string _location = "";
private int level;
public static int HIGH = 2;
public static int MEDIUM = 1;
public static int LOW = 0;
public CeilingFan(string location)
{
this._location = location;
}
public void High()
{
// turns the ceiling fan on to high
level = HIGH;
Console.WriteLine((_location + " ceiling fan is on high"));
}
public void Medium()
{
// turns the ceiling fan on to medium
level = MEDIUM;
Console.WriteLine((_location + " ceiling fan is on medium"));
}
public void Low()
{
// turns the ceiling fan on to low
level = LOW;
Console.WriteLine((_location + " ceiling fan is on low"));
}
public void Off()
{
// turns the ceiling fan off
level = 0;
Console.WriteLine((_location + " ceiling fan is off"));
}
public int GetSpeed()
{
return level;
}
}
internal class CeilingFanOnCommand : ICommand
{
private CeilingFan _ceilingFan;
public CeilingFanOnCommand(CeilingFan ceilingFan)
{
this._ceilingFan = ceilingFan;
}
public void Execute()
{
this._ceilingFan.High();
}
}
internal class CeilingFanOffCommand : ICommand
{
private CeilingFan _ceilingFan;
public CeilingFanOffCommand(CeilingFan ceilingFan)
{
this._ceilingFan = ceilingFan;
}
public void Execute()
{
_ceilingFan.Off();
}
}
以下都是相關的 Receiver 及 Concrete Command
GarageDoor.cs
GarageDoorDownCommand.cs
GarageDoorUpCommand.cs
Hottub.cs
HottubOffCommand.cs
HottubOnCommand.cs
Light.cs
LightOffCommand.cs
LightOnCommand.cs
LivingroomLightOffCommand.cs
LivingroomLightOnCommand.cs
Stereo.cs
StereoOffCommand.cs
StereoOnWithCDCommand.cs
TV.cs
RemoteControl 為 Invoker ,負責命令的設定、佇列命令及依序執行。
RemoteControl.cs
public class RemoteControl
{
private Dictionary<int, ICommand> _onCommands;
private Dictionary<int, ICommand> _offCommands;
public RemoteControl()
{
_onCommands = new Dictionary<int, ICommand>();
_offCommands = new Dictionary<int, ICommand>();
ICommand noCommand = new NoCommand();
for (int i = 0; i < 7; i++)
{
_onCommands[i] = noCommand;
_offCommands[i] = noCommand;
}
}
public void SetCommand(int slot, ICommand onCommand, ICommand offCommand)
{
_onCommands[slot] = onCommand;
_offCommands[slot] = offCommand;
}
public void OnButtonWasPushed(int slot)
{
_onCommands[slot].Execute();
}
public void OffButtonWasPushed(int slot)
{
_offCommands[slot].Execute();
}
}
RemoteLoader 為 Client ,但意義上不是客戶端,更像是命令模式的環境管理,最主要的責任就是建立 Concrete Command 以及組裝 Receiver。
RemoteLoader.cs
public static void Main(string[] args)
{
// Client 所負責
CeilingFan ceilingFan = new CeilingFan("Living Room");
CeilingFanOnCommand ceilingFanOn = new CeilingFanOnCommand(ceilingFan);
CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingFan);
// Invoker 所負責
RemoteControl remoteControl = new RemoteControl();
remoteControl.SetCommand(2, ceilingFanOn, ceilingFanOff);
Console.WriteLine(remoteControl);
// 使用者的呼叫命令行為
remoteControl.OnButtonWasPushed(2);
remoteControl.OffButtonWasPushed(2);
}
小結
✔️將變動的部分封裝起來
✔️讓需要互動的物件之間的關係鬆綁
✔️類別應該對需求保持開放;應該對既有的程式碼修改保持關閉
命令、接受命令、執行命令都封裝起來,碰到改變互不影響
✔️多用合成,少用繼承
Invoker 合成了 Receiver 與 Concrete Command
✔️針對介面撰寫,而非針對實踐方式撰寫。
Concrete Command 是針對 Command Interface 撰寫,實踐方式則是封裝在 Receiver