意圖
使多個對象都有機會處理請求,從而避免請求的發送者和接受者之間的耦合關系。將這些對象連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個對象能處理請求為止。
場景
假設我們在制作一個游戲的客服系統,客服有三種角色,分別是普通客服、客服經理和客服總監。玩家在網站中提問後,根據問題的分類和重要性處理的流程不一樣。規則如下:
l 分類為1(游戲問題)和2(角色問題)的問題會由普通客服處理。
l 分類為3(充值問題)和4(賬戶問題)的問題會由客服經理處理。
l 普通客服和客服經理都沒有能處理的問題由客服總監進行處理。
l 所有的問題都分為普通問題和重要問題,如果問題是重要問題則需要上一級的客服進行審核(普通客服回復的問題需要客服經理審核、客服經理回答的問題需要客服總監審核)。
這個處理的業務規則比較復雜,您可能會想到創建三個具體客服類型繼承抽象客服,然後根據問題的性質傳給不同的具體客服來處理。可是這樣做有幾個缺點:
l 客戶端需要知道三個角色能處理的問題屬性,三個角色能處理的問題應該只有三個角色自己清楚就可以了。
l 客戶端需要和三個角色發生耦合,並且在一個處理過程中會先後調用不同的角色。普通客服在回復了重要問題後應該通知客服經理來審核,現在這個過程交給了客戶端去做
客戶端知道了太多客服處理問題的細節,對於玩家來說他只需要知道把這些問題告訴客服人員並且得到客服人員的處理結果,具體背後的處理流程是怎麼樣的它不用知道。由此,引入責任鏈模式來解決這些問題。
示例代碼
using System;
using System.Collections.Generic;
using System.Text;
namespace ChainOfRespExample
{
class Program
{
static List<CustomerService> gmTeam = new List<CustomerService>();
static List<CustomerService> managerTeam = new List<CustomerService>();
static List<CustomerService> directorTeam = new List<CustomerService>();
static Random random = new Random();
static void InitCOR()
{
if (managerTeam.Count > 0)
{
foreach (CustomerService cs in gmTeam)
cs.SetLeader(managerTeam[random.Next(managerTeam.Count)]);
}
else
{
foreach (CustomerService cs in gmTeam)
cs.SetLeader(directorTeam[random.Next(directorTeam.Count)]);
}
foreach (CustomerService cs in managerTeam)
cs.SetLeader(directorTeam[random.Next(directorTeam.Count)]);
// These configs above depends on business logic.
}
static void InitGM()
{
for (int i = 1; i <= 9; i++)
{
CustomerService gm = new NormalGM("gm" + i);
gm.SetResponsibleCaseCategory(new int[] { 1, 2 });
gmTeam.Add(gm);
}
for (int i = 1; i <= 2; i++)
{
CustomerService manager = new GMManager("manager" + i);
manager.SetResponsibleCaseCategory(new int[] { 3, 4 });
managerTeam.Add(manager);
}
for (int i = 1; i <=1; i++)
directorTeam.Add(new GMDirector("director" + i));
// These configs above should be from database.
}
static void Main(string[] args)
{
InitGM();
InitCOR();
CustomerService gm = gmTeam[random.Next(gmTeam.Count)];
gm.HandleCase(new Case(1, false));
Console.WriteLine(Environment.NewLine);
gm = gmTeam[random.Next(gmTeam.Count)];
gm.HandleCase(new Case(2, true));
Console.WriteLine(Environment.NewLine);
gm = gmTeam[random.Next(gmTeam.Count)];
gm.HandleCase(new Case(3, false));
Console.WriteLine(Environment.NewLine);
gm = gmTeam[random.Next(gmTeam.Count)];
gm.HandleCase(new Case(4, true));
Console.WriteLine(Environment.NewLine);
gm = gmTeam[random.Next(gmTeam.Count)];
gm.HandleCase(new Case(5, true));
}
}
class Case
{
private int caseCategory;
public int CaseCategory
{
get { return caseCategory; }
}
private bool importantCase;
public bool ImportantCase
{
get { return importantCase; }
}
private string reply;
public string Reply
{
get { return reply ; }
set { reply = value; }
}
public Case(int caseCategory, bool importantCase)
{
this.caseCategory = caseCategory;
this.importantCase = importantCase;
}
}
abstract class CustomerService
{
protected CustomerService leader;
protected List<int> responsibleCaseCategory = new List<int>();
protected string name;
public string Name
{
get { return name; }
}
public CustomerService(string name)
{
this.name = name;
}
public void SetLeader(CustomerService leader)
{
this.leader = leader;
}
public abstract void HandleCase(Case gameCase);
public void SetResponsibleCaseCategory(int[] responsibleCaseCategory)
{
foreach (int i in responsibleCaseCategory)
this.responsibleCaseCategory.Add(i);
}
}
class NormalGM : CustomerService
{
public NormalGM(string name) : base(name) { }
public override void HandleCase(Case gameCase)
{
if (responsibleCaseCategory.Contains(gameCase.CaseCategory))
{
gameCase.Reply = "OK";
Console.WriteLine("The case has been replied by " + name);
if (gameCase.ImportantCase)
{
Console.WriteLine("The reply should be checked.");
leader.HandleCase(gameCase);
}
else
Console.WriteLine("The reply has been sent to player.");
}
else if (leader != null )
{
Console.WriteLine(string.Format("{0} reports this case to {1}.", name, leader.Name));
leader.HandleCase(gameCase);
}
}
}
class GMManager : CustomerService
{
public GMManager(string name) : base(name) { }
public override void HandleCase(Case gameCase)
{
if (responsibleCaseCategory.Contains(gameCase.CaseCategory))
{
gameCase.Reply = "OK";
Console.WriteLine("The case has been replied by " + name);
if (gameCase.ImportantCase)
{
Console.WriteLine("The reply should be checked.");
leader.HandleCase(gameCase);
}
else
Console.WriteLine("The reply has been sent to player.");
}
else if (gameCase.Reply != null )
{
Console.WriteLine("The case has been checked by " + name);
Console.WriteLine("The reply has been sent to player.");
}
else if (leader != null )
{
Console.WriteLine(string.Format("{0} reports this case to {1}.", name, leader.Name));
leader.HandleCase(gameCase);
}
}
}
class GMDirector : CustomerService
{
public GMDirector(string name) : base(name) { }
public override void HandleCase(Case gameCase)
{
if (gameCase.Reply != null )
{
Console.WriteLine("The case has been checked by " + name);
Console.WriteLine("The reply has been sent to player.");
}
else
{
gameCase.Reply = "OK";
Console.WriteLine("The case has been replied by " + name);
Console.WriteLine("The reply has been sent to player.");
}
}
}
}
代碼執行結果如下圖(後面一圖為注釋掉InitGM()方法中初始化managerTeam代碼的執行結果):
代碼說明
l Case類代表了問題。CaseCategory屬性代表了問題的分類,普通客服和客服經理處理不同分類的問題。ImportantCase屬性代表了問題是否是重要問題,如果是重要問題,則需要上級領導審核。Reply屬性代表了客服對問題的回復。
l CustomerService類是責任鏈模式中的抽象處理者。我們看到,它定義了下個責任人的引用,並且提供了設置這個責任人的方法。當然,它也定義了統一的處理接口。
l NormalGM是具體處理者,它實現了處理接口。在這裡,我們看到普通客服的處理邏輯是,如果這個問題的分類在它負責的分類之外則直接提交給上級領導進行處理(把對象通過責任鏈傳遞),否則就回復問題,回復問題之後看這個問題是否是重要問題,如果是重要問題則給上級領導進行審核,否則問題處理結束。
l GMManager也是一個具體處理者。客服經理處理問題的邏輯是,首先判斷問題的問題是否在它負責的分類之內,如果是的話則進行處理(重要問題同樣提交給上級處理),如果不是的話就看問題是否有了回復,如果有回復說明是要求審核的問題,審核後問題回復,如果問題還沒有回復則提交給上級處理。
l GMDirector的處理流程就相對簡單了,它並沒有上級了,因此所有問題必須在它這裡結束。對於沒有回復的問題則進行回復,對於要求審核的問題則進行審核。
l 再來看看客戶端的調用。首先,執行了InitGM()方法來初始化客服團隊的數據,在這裡我們的團隊中有9個普通客服、2個客服經理和1個客服總監。普通客服只能回復分類1和分類2的問題,而客服經理只能回復分類3和分類4的問題。
l 然後,調用InitCOR()方法來初始化責任鏈,在這裡我們並沒有簡單得設置普通客服的上級是客服經理、客服經理的上級是客服總監,而是自動根據是否有客服經理這個角色來動態調整責任鏈,也就是說如果沒有客服經理的話,普通客服直接向客服總監匯報。
l 最後,我們模擬了一些問題數據進行處理。對於客戶端(玩家)來說任何普通客服角色都是一樣的,因此我們為所有問題隨機分配了普通客服作為責任鏈的入口點。
l 首先來分析有客服經理時的處理情況。分類為1的問題直接由普通客服處理完畢。分類為2的重要問題由普通客服回復後再由客服經理進行審核。分類為3的問題直接由普通客服提交到客服經理進行處理。分類為4的重要問題也直接由普通客服提交到客服經理進行處理,客服經理回復後再提交到客服總監進行審核。分類為5的問題由普通客服提交到客服經理進行處理,客服經理再提交給客服總監進行處理。
l 再來分析沒有客服經理時的處理情況。分類為1的問題直接由普通客服處理完畢。分類為2的重要問題由普通客服回復後再由客服總監進行審核。分類為3的問題直接由普通客服提交到客服總監進行處理。分類為4的重要問題也直接由普通客服提交到客服總監進行處理。分類為5的問題也直接由普通客服提交到客服總監進行處理。
l 如果沒有責任鏈模式,這個處理流程將會多麼混亂。
何時采用
l 從代碼角度來說,如果一個邏輯的處理由不同責任對象完成,客戶端希望能自定義這個處理流程並且不希望直接和多個責任對象發生耦合的時候可以考慮責任鏈模式。
l 從應用角度來說,如果對一個事情的處理存在一個流程,需要經歷不同的責任點進行處理,並且這個流程比較復雜或只希望對外公開一個流程的入口點的話可以考慮責任鏈模式。其實,責任鏈模式還可以在構架的層次進行應用,比如.NET中的層次異常處理關系就可以看作是一種責任鏈模式。
實現要點
l 有一個抽象責任角色,避免各責任類型之間發生耦合。
l 抽象責任角色中定義了後繼責任角色,並對外提供一個方法供客戶端配置。
l 各具體責任類型根據待處理對象的狀態結合自己的責任范圍來判斷是否能處理對象,如果不能處理提交給上級責任人處理(也就是純的責任模式,要麼自己處理要麼提交給別人)。當然,也可以在進行部分處理後再提交給上級處理(也就是不純的責任鏈模式)。
l 需要在客戶端鏈接各個責任對象,如果鏈接的不恰當,可能會導致部分對象不能被任何一個責任對象進行處理。
注意事項
l 責任鏈模式和狀態模式的區別在於,前者注重責任的傳遞,並且責任鏈由客戶端進行配置,後者注重對象狀態的轉換,這個轉換過程對客戶端是透明的。