Home [閱讀筆記][Design Pattern] Ch23.命令模式(Command)
Post
Cancel

[閱讀筆記][Design Pattern] Ch23.命令模式(Command)

命令模式(Command)

命令模式(Command),將一個請求封裝為一個物件,讓你可用不同請求對客戶進行參數化;對請求排隊或記錄請求日誌,以及支援可取消的操作。

結構

  • Command類別:用來宣告執行操作的介面。
  • ConcreteCommand類別:將一個接收者物件綁定於一個動作,呼叫接收者相應的操作,以實現Excute
  • Invoker類別:要求該命令執行這個請求。
  • Receiver類別:知道如何實施與執行一個請求相關的操作,任何類別都可能做為一個接收者。
  • 用戶端程式碼:建立一個具體命令物件並設定它的接收者。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Client

    Invoker 要求該命令執行這個請求

        Command <<interface>> 用來宣告執行操作的介面
        + Excute()

        ConcreteCommand 將一個接收者物件綁定於一個動作,呼叫接收者相應的操作,以實現Excute
        - receiver: Receiver
        + Excute()

    Receiver 知道如何實施與執行一個請求相關的操作,任何類別都可能做為一個接收者
    + Action()

        

程式碼

Command

Command類別:用來宣告執行操作的介面。

1
2
3
4
5
6
7
abstract class Command {
    protected Receiver receiver;
    public Command(Receiver receiver) {
        this.receiver = receiver;
    }
    public abstract void Execute();
}

ConcreteCommand

ConcreteCommand類別:將一個接收者物件綁定於一個動作,呼叫接收者相應的操作,以實現Excute

1
2
3
4
5
6
class ConcreteCommand: Command {
    public ConcreteCommand(Receiver receiver): base(receiver) { }
    public override void Execute() {
        receiver.Action();
    }
}

Invoker

Invoker類別:要求該命令執行這個請求。

1
2
3
4
5
6
7
8
9
class Invoker {
    private Command cmd;
    public void SetCommand(Command cmd) {
        this.cmd = cmd;
    }
    public void ExcuteCommand() {
        cmd.Excute();
    }
}

Receiver

Receiver類別:知道如何實施與執行一個請求相關的操作,任何類別都可能做為一個接收者。

1
2
3
4
5
class Receiver {
    public void Action() {
        Conosle.WriteLine("執行請求!");
    }
}

用戶端程式碼

用戶端程式碼:建立一個具體命令物件並設定它的接收者。

1
2
3
4
5
6
Receiver r = new Receiver();
Command c = new ConcreteCommand(r);
Invoker i = new Invoker();

i.SetCommand(c);
i.ExcuteCommand();

命令模式作用

優點

  • 它較容易地設計一個命令佇列
  • 在需要的情況下,可以較容易地將命令記入日誌
  • 允許接收請求的一方決定是否要否決請求
  • 可以容易地實現對請求的取消和重做
  • 由於加進新的具體命令類別不影響其他的類別,因此增加新的具體命令類別很容易

最關鍵的優點就是命令模式把請求一個操作的物件與知道怎麼執行一個操作的物件分割開。

何時用?

敏捷開發原則告訴我們,不要為程式碼加上基於猜測的、實際不需要的功能。

如果不清楚一個系統是否需要命令模式,就不要急著去實現它,事實上,在需要的時候透過重構實現這個模式並不困難,只有在真正需要如取消/恢復操作等功能時,把原來的程式碼重構為命令模式才有意義。

v1.0 緊耦合

「緊耦合」容易出錯,容易混亂。

結構

1
2
3
4
5
客戶端類別

烤肉串者
+ 烤羊肉串()
+ 烤G翅()

程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//烤肉串者
public class Barbecuer {
    //烤羊肉串
    public void BakeMutton() {
        Console.WriteLine("烤羊肉串!");
    }
    //烤G翅
    public void BakeChickenWing() {
        Console.WriteLine("烤G翅!");
    }
}

//用戶端
//用戶端程式與「烤肉串者」緊耦合,儘管簡單,但卻極為僵化,有許多的隱憂
Barbecuer boy = new Barbecuer();
boy.BakeMutton();
boy.BakeMutton();
boy.BakeChickenWing();
boy.BakeMutton();
boy.BakeMutton();
boy.BakeChickenWing();

v2.0 鬆耦合(Draft)

結構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
客戶端類別

服務生
+ 設定命令(in command: 命令)
+ 通知執行()

    命令
    + 執行命令()

        烤羊肉串命令
        + 執行命令()

        烤G翅命令
        + 執行命令()
烤肉串者
+ 烤羊肉串()
+ 烤G翅()

程式碼

烤肉串者

1
2
3
4
5
6
7
8
9
10
11
//烤肉串者
public class Barbecuer {
    //烤羊肉串
    public void BakeMutton() {
        Console.WriteLine("烤羊肉串!");
    }
    //烤G翅
    public void BakeChickenWing() {
        Console.WriteLine("烤G翅!");
    }
}

抽象命令類別

1
2
3
4
5
6
7
8
9
10
11
12
//抽象命令類別
public abstract class Command {
    protected Barbecuer receiver;

    //只需要確定「烤肉串者」是誰
    public Command(Barbecuer receiver) {
        this.receiver = receiver;
    }

    //執行命令
    public abstract void ExcuteCommand();
}

具體命令類別

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//具體命令類別
//烤羊肉串命令
class BakeMuttonCommand: Command {
    public BakeMuttonCommand(Barbecuer receiver): base(receiver) { }

    //執行烤羊肉串
    public override void ExcuteCommand() {
        receiver.BakeMutton();
    }
}

//烤G翅命令
class BakeChickenWingCommand: Command {
    public BakeChickenWingCommand(Barbecuer receiver): base(receiver) { }

    //執行烤G翅
    public override void ExcuteCommand() {
        receiver.BakeChickenWing();
    }
}

服務生類別

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Waiter {
    private Command command;

    //設定訂單
    //不管用戶想要什麼烤肉,反正都是「命令」,只管記錄訂單,然後通知「烤肉串者」執行即可。
    public void SetOrder(Command command) {
        this.command = command;
    }

    //通知執行
    public void Notify() {
        command.ExcuteCommand();
    }
}

用戶端實現

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void Main()
{
    //開店前準備
    Barbecuer boy = new Barbecuer();
    Command BakeMuttonCommand1 = new BakeMuttonCommand(boy); //烤羊肉串(第一支)
    Command BakeMuttonCommand2 = new BakeMuttonCommand(boy); //烤羊肉串(第二支)
    Command BakeChickenWingCommand = new BakeChickenWingCommand(boy); //烤G翅
    Waiter gril = new Waiter();

    //開店營業
    gril.SetOrder(BakeMuttonCommand1);
    gril.Notify();
    gril.SetOrder(BakeMuttonCommand2);
    gril.Notify();
    gril.SetOrder(BakeChickenWingCommand);
    gril.Notify();
}

v3.0 鬆耦合

服務生類別

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//服務生類別
public class Waiter {
    //增加存放具體命令的容器
    private IList<Command> orders = new List<Command>();

    //設定訂單
    //不管用戶想要什麼烤肉,反正都是「命令」,只管記錄訂單,然後通知「烤肉串者」執行即可。
    public void SetOrder(Command command) {
        //在向戶提出請求時,對沒貨的燒烤進行回絕
        if(command.ToString()== "BakeChickenWingCommand") {
            Console.WriteLine("服務生:G翅沒了,請點別的燒烤。");
			return;
        }
        //增加訂單
        orders.Add(command);
        Console.WriteLine($"增加訂單: {command.ToString()},時間: {DateTime.Now}");
    }

    //取消訂單
    public void CancelOrder(Command command) {
        orders.Remove(command);
        Console.WriteLine($"取消訂單: {command.ToString()},時間: {DateTime.Now}");
    }

    //通知全部執行
    public void Notify() {
        foreach(Command cmd in orders) {
            cmd.ExcuteCommand();
        }
    }
}

用戶端實現

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void Main()
{
    //開店前準備
    Barbecuer boy = new Barbecuer();
    Command BakeMuttonCommand1 = new BakeMuttonCommand(boy);
    Command BakeMuttonCommand2 = new BakeMuttonCommand(boy);
    Command BakeChickenWingCommand1 = new BakeChickenWingCommand(boy);	
    Waiter gril = new Waiter();

    //開店營業 顧客點菜
    gril.SetOrder(BakeMuttonCommand1);
    gril.SetOrder(BakeMuttonCommand2);
    gril.SetOrder(BakeChickenWingCommand1);

    //點餐完畢,通知廚房
    gril.Notify();
}

/* 執行結果:

增加訂單: 烤羊肉串,時間: 03/12/2009 06:58:31
增加訂單: 烤羊肉串,時間: 03/12/2009 06:58:31
服務生:G翅沒了,請點別的燒烤。
廚房製作-烤羊肉串!
廚房製作-烤羊肉串!
*/
This post is licensed under CC BY 4.0 by the author.

[閱讀筆記][Design Pattern] Ch22.橋接模式(Bridge)

[閱讀筆記][Design Pattern] Ch24.職責鏈模式(Chain of Responsibility)