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

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

職責鏈模式(Chain of Responsibility)

職責鏈模式(Chain of Responsibility),使多個物件都有機會處理請求,從而避免請良的發送者和接收者之間的耦合關係。將這物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個物件處理它為止。

類別有太多的責任,這違背了「單一職責原則(Single Responsibility Principle)」,增加新的管理類別,需要修改這個類別,違背了「開放封閉原則 Open-Closed Principle (OCP)」。

結構

  • Handler 定義一個處理請示的介面
  • ConcreteHandler 具體處理者類別,處理它所負責的請求,可使用它的後繼者,如果可處理該請求,就處理之,否則就將該請求轉發給它的後繼者
1
2
3
4
5
6
7
8
9
10
11
Client

    Handler 定義一個處理請示的介面
    + SetSuccessor(in successor: Handler)
    + HandleRequest(in request: int)

        ConcreteHandler1 具體處理者類別,處理它所負責的請求,可使用它的後繼者,如果可處理該請求,就處理之,否則就將該請求轉發給它的後繼者
        + HandleRequest(in request: int)

        ConcreteHandler2
        + HandleRequest(in request: int)

程式碼

Handler

Handler 定義一個處理請示的介面。

1
2
3
4
5
6
7
8
9
10
11
abstract class Handler {
    protected Handler successor;

    //設定繼任者
    public void SetSuccessor(Handler successor) {
        this.successor = successor;
    }

    //處理請求的抽象方法
    public abstract void HandleRequest(int resquest);
}

ConcreteHandler

ConcreteHandler 具體處理者類別,處理它所負責的請求,可使用它的後繼者,如果可處理該請求,就處理之,否則就將該請求轉發給它的後繼者。

ConcreteHandler1

ConcreteHandler1 當請求數在0到10之間則有權處理,否則轉到下一位。

1
2
3
4
5
6
7
8
9
10
11
class ConcreteHandler1: Handler {
    public override void HandleRequest(int resquest) {
        //0-10處理此請求
        if(resquest >= 0 && resquest <= 10) {
            Console.WriteLine($"{this.GetType().Name} 處理請求 {resquest}");
        } //轉移到下一位 
        else if(successor != null) {
            successor.HandleRequest(resquest);
        }
    }
}

ConcreteHandler2

ConcreteHandler2 當請求數在10到20之間則有權處理,否則轉到下一位。

1
2
3
4
5
6
7
8
9
10
11
class ConcreteHandler2: Handler {
    public override void HandleRequest(int resquest) {
        //10-20處理此請求
        if(resquest > 10 && resquest <= 20) {
            Console.WriteLine($"{this.GetType().Name} 處理請求 {resquest}");
        } //轉移到下一位 
        else if(successor != null) {
            successor.HandleRequest(resquest);
        }
    }
}

ConcreteHandler3

ConcreteHandler3 當請求數在20到30之間則有權處理,否則轉到下一位。

1
2
3
4
5
6
7
8
9
10
11
class ConcreteHandler3: Handler {
    public override void HandleRequest(int resquest) {
        //20-30處理此請求
        if(resquest > 20 && resquest <= 30) {
            Console.WriteLine($"{this.GetType().Name} 處理請求 {resquest}");
        } //轉移到下一位 
        else if(successor != null) {
            successor.HandleRequest(resquest);
        }
    }
}

用戶端程式碼

用戶端程式碼,向鏈上的具體處理者物件提交請求。

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
public static void Main()
{
    Handler h1 = new ConcreteHandler1();
    Handler h2 = new ConcreteHandler2();
    Handler h3 = new ConcreteHandler3();

    //設定職責鏈上家與下家
    h1.SetSuccessor(h2);
    h2.SetSuccessor(h3);

    int[] requests = {2,9,14,22,18,3,27,20};

    //迴圈給最小處理者提交請求,不同的數額,由不同許可權處理者處理
    foreach(int r in requests) {
        h1.HandleRequest(r);
    }
}

/* 執行結果
ConcreteHandler1 處理請求 2
ConcreteHandler1 處理請求 9
ConcreteHandler2 處理請求 14
ConcreteHandler3 處理請求 22
ConcreteHandler2 處理請求 18
ConcreteHandler1 處理請求 3
ConcreteHandler3 處理請求 27
ConcreteHandler2 處理請求 20
*/

職責鏈的好處

這當中最關鍵的是:當客戶提交一個請求時,請求是沿鍵傳遞直到有一個ConcreteHandler物件負責處理它。

這樣的好處是說:請求者不用管哪個物件來處理,反正該請求會被處理就對了。

這就使得接收者和發送者都沒有對方的明確資訊,且鏈中的物件自己也不知道鏈的結構。結果是職責鏈可簡化物件的相互連接,它們僅需保持一個指向其後繼者的參考,而不需保持它所有的候選接受者的參考,也就大大降低了耦合度了。

由於是用戶端來定義鏈的結構,我們可以隨時地增加或修改處理一個請求的結構。增強了給物件指派職責的靈活性。

注意

雖然很靈活,不過也要當心,一個請求極有可能到了鏈的末端都得不到處理,或者因為沒有正確配置而得不到處理,這就很糟糕了。需要事先考慮全面。

這就跟現實中郵件一封信,因地址不對,最終無法送達一樣。

重點

  • 你需要事先給每個具體管理者設定他的上司是哪個類別,也就是設定後繼者。
  • 你需要在每個具體管理者處理請求時,做出判斷,是可以處理這個請求,還是必須要「推卸責任」,轉移給後繼者去處理。

v1.0 加薪程式-初步

申請

1
2
3
4
5
class Request {
    public string RequestType { get; set; } //申請類別
    public string Content { get; set; } //申請內容
    public int Number { get; set; } //數量
}

管理者

比較長的方法,多條的分支,這些其實都是程式壞味道。

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
32
33
34
35
36
class Manager {
    string name;
    public Manager(string name) {
        this.name = name;
    }

    //得到結果
    //比較長的方法,多條的分支,這些其實都是程式壞味道。
    public void GetResult(string managerLevel, Request request) {
        switch (managerLevel) {
            case "經理":
                if(request.RequestType == "請假" && request.Number <= 2) {
                    Console.WriteLine($"{name}: {request.Content} 休假數 {request.Number} 被批准。");
                } else {
                    Console.WriteLine($"{name}: {request.Content} 休假數 {request.Number} 我無權處理");
                }
            break;
            case "總監":
                if(request.RequestType == "請假" && request.Number <= 5) {
                    Console.WriteLine($"{name}: {request.Content} 休假數 {request.Number} 被批准。");
                } else {
                    Console.WriteLine($"{name}: {request.Content} 休假數 {request.Number} 我無權處理");
                }
            break;
            case "總經理":
                if(request.RequestType == "請假") {
                    Console.WriteLine($"{name}: {request.Content} 休假數 {request.Number} 被批准。");
                } else if(request.RequestType == "加薪" && request.Number <= 500) {
                    Console.WriteLine($"{name}: {request.Content} 加薪 ${request.Number} 被批准。");
                } else if(request.RequestType == "加薪" && request.Number > 500) {
                    Console.WriteLine($"{name}: {request.Content} 加薪 ${request.Number} 再說吧。");
                }
            break;
        }
    }
}

用戶端程式

寫得不好:(TODO)

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
//三個管理者
Manager ken = new Manager("Ken"); //經理
Manager leo = new Manager("Leo"); //總監
Manager jim = new Manager("Jim"); //總經理

//小菜請求加薪
Request request = new Request();
request.RequestType = "加薪";
request.Content = "小菜請求加薪";
request.Number = 1000;

//不同級別對此請求做判斷和處理
ken.GetResult("經理",request);
leo.GetResult("總監",request);
jim.GetResult("總經理",request);

//小菜請假3天
Request request = new Request();
request.RequestType = "請假";
request.Content = "小菜請假";
request.Number = 3;
//不同級別對此請求做判斷和處理
ken.GetResult("經理",request);
leo.GetResult("總監",request);
jim.GetResult("總經理",request);

v2.0 加薪程式-重構

把管理者類別當中的那些分支 分解到每一個具體的管理者類別當中,然後利用事先設定的後繼者來實現請求處理的許可權問題。

結構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
管理者
+ 設定管理者上級()
+ 申請請求(in request: 申請)

    經理
    + 申請請求(in request: 申請)

    總監
    + 申請請求(in request: 申請)

    總經理
    + 申請請求(in request: 申請)

申請
+ 申請類別: string
+ 申請內容: string
+ 數量: int

程式碼

申請

1
2
3
4
5
class Request {
    public string RequestType { get; set; } //申請類別
    public string Content { get; set; } //申請內容
    public int Number { get; set; } //數量
}

管理者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract class Manager {
    protected string name;
    protected Manager superior; //管理者的上級
    public Manager(string name) {
        this.name = name;
    }

    //設定管理者的上級(關鍵的方法)
    public void SetSuperior(Manager superior) {
        this.superior = superior;
    }

    //申請請求
    public abstract void RequestApplications(Request request);
}

經理類別

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class CommonManager: Manager {
    public CommonManager(string name): base(name) { }

    public override void RequestApplications(Request request) {
        //經理的權限:可准許下屬兩天內的假期
        if(request.RequestType == "請假" && request.Number <= 2) {
            Console.WriteLine($"{name}: {request.Content} 休假數 {request.Number} 被批准。");
            return;
        }  
        //其餘的申請都需轉到上級
        if(superior != null) {
            superior.RequestApplications(request);
        }
    }
}

總監類別

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Majordomo: Manager {
    public Majordomo(string name): base(name) { }

    public override void RequestApplications(Request request) {
        //總監的權限:可准許下屬5天內的假期
        if(request.RequestType == "請假" && request.Number <= 5) {
            Console.WriteLine($"{name}: {request.Content} 休假數 {request.Number} 被批准。");
            return;
        }  
        //其餘的申請都需轉到上級
        if(superior != null) {
            superior.RequestApplications(request);
        }
    }
}

總經理類別

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class GeneralManager: Manager {
    public GeneralManager(string name): base(name) { }

    public override void RequestApplications(Request request) {
        //總經理的權限:可准許下屬任意天的假期
        if(request.RequestType == "請假") {
            Console.WriteLine($"{name}: {request.Content} 休假數 {request.Number} 被批准。");
        } else if(request.RequestType == "加薪" && request.Number <= 500) {
            Console.WriteLine($"{name}: {request.Content} 加薪 ${request.Number} 被批准。");
        } else if(request.RequestType == "加薪" && request.Number > 500) {
            Console.WriteLine($"{name}: {request.Content} 加薪 ${request.Number} 再說吧。");
        }
    }
}

用戶端程式碼

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
32
33
34
35
36
37
38
39
40
41
42
43
public static void Main()
{
    //三個管理者
    CommonManager ken = new CommonManager("Ken"); //經理
    Majordomo leo = new Majordomo("Leo"); //總監
    GeneralManager jim = new GeneralManager("Jim"); //總經理

    //設定上級,完全可以根據實際需求來更改設定
    ken.SetSuperior(leo);
    leo.SetSuperior(jim);

    //用戶端的申請都是由「經理」發起,但實際誰來決策由具體管理類別來處理,用戶端不知道
    Request request = new Request();
    request.RequestType = "請假";
    request.Content = "小菜請假";
    request.Number = 1;
    ken.RequestApplications(request);
            
    Request request2 = new Request();
    request2.RequestType = "請假";
    request2.Content = "小菜請假";
    request2.Number = 5;
    ken.RequestApplications(request2);

    Request request3 = new Request();
    request3.RequestType = "加薪";
    request3.Content = "小菜請求加薪";
    request3.Number = 500;
    ken.RequestApplications(request3);

    Request request4 = new Request();
    request4.RequestType = "加薪";
    request4.Content = "小菜請求加薪";
    request4.Number = 1000;
    ken.RequestApplications(request4);	
}

/*
Ken: 小菜請假 休假數 1 被批准。
Leo: 小菜請假 休假數 5 被批准。
Jim: 小菜請求加薪 加薪 $500 被批准。
Jim: 小菜請求加薪 加薪 $1000 再說吧。
*/

解決原來大量的分支判斷造成難維護、靈活性差的問題。

This post is licensed under CC BY 4.0 by the author.

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

[閱讀筆記][Design Pattern] Ch25.仲介者模式(Mediator)