1. 程式人生 > 程式設計 >C#設計模式之職責鏈模式示例詳解

C#設計模式之職責鏈模式示例詳解

前言

在軟體開發中,我們通常會遇到一種場景,比如某個請求,會依次經過系統中的很多個模組來處理,如果某個模組處理不了,則將請求傳遞給下一個模組,比如在訂單處理中,首先要經過使用者校驗,商品庫存充足校驗,如果不滿足條件,返回錯誤,如果滿足條件才會到下一步處理。

在ASP.NET Core裡有middleware中間鍵的概念,每一個請求進來,都會經過一系列的Handler,這是一種職責鏈模式,每一個Handler都會決定是否處理該請求,以及是否決定將該請求傳遞給一下請求繼續處理。

在.NET的委託中,也有一個委託鏈概念,當多個物件註冊同一事件時,物件將委託放在一個鏈上,依次處理。

在JavaScript或者WPF的事件模型中,事件有冒泡和下沉,事件能夠逐個向上級或者下級物件傳遞,每個物件都會決定是否會對該事件進行迴應,或者終止事件的繼續傳遞。

這些都是典型的職責鏈模式,責任鏈模式為請求建立了一個接收者物件的鏈,每個接收者都包含對另一個接收者的引用。如果一個物件不能處理該請求,那麼它會把相同的請求傳給下一個接收者,沿著這條鏈傳遞請求,直到有物件處理它為止。發出這個請求的客戶端並不知道鏈上的哪一個物件最終處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織和分配責任。

示例1

假設在一個電腦遊戲中,每個角色都有兩個屬性:攻擊值和防禦值。

public class Creature
{
 public string Name;
 public int Attack,Defense;
 public Creature(string name,int attack,int defense)
 {
 Name = name;
 Attack = attack;
 Defense = defense;
 }

 public override string ToString()
 {
 return $"Name:{Name} Attack:{Attack} Defense:{Defense}";
 }
}

現在這個角色會在遊戲中進行活動,他可能會撿到一些武器增加自己的攻擊值或者防禦值。我們通過CreatureModifer來修改該物件的攻擊值或者防禦值。通常,在遊戲中會有多個修改器會對同一角色進行修改,比如撿到武器A,然後撿到武器B等等,因此我們可以將這些修改器按照順序附加到Creature物件上進行逐個修改。

在經典的職責鏈實現模式中,可以向下面這種方式來定義CreatureModifier物件:

public class CreatureModifier
{
 protected Creature creature;
 protected CreatureModifier next;
 public CreatureModifier(Creature creature)
 {
 this.creature = creature;
 }
 public void Add(CreatureModifier m)
 {
 if (next != null)
 {
 next.Add(m);
 }
 else
 {
 next = m;
 }
 }
 public virtual void Handle()
 {
 next?.Handle();
 }
}

在這個類中:

  • 建構函式裡儲存對待修改物件Creature的引用。
  • 該類沒有做多少工作,但他不是抽象類,其他類可以繼承該物件。
  • Add方法可以新增其他CreatureModifier類到處理鏈條上。如果當前修改物件next物件為空,則賦值,否則將他新增到處理鏈的末端。
  • Handle方法簡單呼叫下個處理鏈上物件的Handle方法。子類可以重寫該方法以實現具體的操作。

現在,可以定義一個具體的修改類了,這個類可以將角色的攻擊值翻倍。

public class DoubleAttackModifier : CreatureModifier
{
 public DoubleAttackModifier(Creature c) : base(c)
 {
 }

 public override void Handle()
 {
 creature.Attack *= 2;
 Console.WriteLine($"Doubling {creature.Name}'s attack,Attack:{creature.Attack},Defense:{creature.Defense}");
 base.Handle();
 }
}

該類繼承自CreatureModifier類,並重寫了Handle方法,在方法裡做了兩件事,一是將Attack屬性翻倍,另外一個非常重要,就是呼叫了基類的Handle方法,讓職責鏈上的修改器繼續進行下去。千萬不要忘記呼叫,否則鏈條在這裡就會終止了,不會繼續往下傳遞了。

接著,新建一個增加防禦值的修改器,如果攻擊值小於2,則防禦值增加1:

public class IncreaseDefenseModifier : CreatureModifier
{
 public IncreaseDefenseModifier(Creature creature) : base(creature)
 {
 }
 public override void Handle()
 {
 if (creature.Attack <= 2)
 {
 Console.WriteLine($"Increasing {creature.Name}'s defense");
 creature.Defense++;
 }
 base.Handle();
 }
}

現在整個應用程式碼如下:

Creature creature = new Creature("yy",1,1);
Console.WriteLine(creature);
CreatureModifier modi = new CreatureModifier(creature);
modi.Add(new DoubleAttackModifier(creature));//attack 2,defense 1
modi.Add(new DoubleAttackModifier(creature));//attack 4,defense 1
modi.Add(new IncreaseDefenseModifier(creature));//attack 4,defense 1
modi.Handle();

可以看到,第三個IncreaseDefenseModifier因為不滿足attack小於等於2的條件,所以Defense沒有修改。

示例2

下面這個例子來自 https://refactoring.guru/ ,首先定義一個包含用來建立處理鏈的方法,也定義一個處理請求的方法:

public interface IHandle
{
 IHandle SetNext(IHandle handle);
 object Handle(object request);
}

再定義一個抽象類,用來設定職責鏈和處理職責鏈上的請求。

public abstract class AbstractHandle : IHandle
{
 private IHandle _nextHandle;

 public IHandle SetNext(IHandle handle)
 {
 this._nextHandle = handle;
 return handle;
 }

 public virtual object Handle(object request)
 {
 if (this._nextHandle != null)
 {
 return this._nextHandle.Handle(request);
 }
 else
 {
 return null;
 }
 }
}

在定義幾個具體的職責鏈上處理的具體類:

public class MonkeyHandle : AbstractHandle
{
 public override object Handle(object request)
 {
 if (request.ToString() == "Banana")
 {
  return $"Monkey: I'll eat the {request.ToString()}.\n";
 }
 else
 {
  return base.Handle(request);
 }
 }
}

public class SquirrelHandler : AbstractHandle
{
 public override object Handle(object request)
 {
 if (request.ToString() == "Nut")
 {
  return $"Squirrel: I'll eat the {request.ToString()}.\n";
 }
 else
 {
  return base.Handle(request);
 }
 }
}

public class DogHandler : AbstractHandle
{
 public override object Handle(object request)
 {
 if (request.ToString() == "MeatBall")
 {
  return $"Dog: I'll eat the {request.ToString()}.\n";
 }
 else
 {
  return base.Handle(request);
 }
 }
}

再定義使用方法,引數為單個職責鏈引數:

public static void ClientCode(AbstractHandler handler)
{
 foreach (var food in new List<string> { "Nut","Banana","Cup of coffee" })
 {
 Console.WriteLine($"Client: Who wants a {food}?");

 var result = handler.Handle(food);

 if (result != null)
 {
  Console.Write($" {result}");
 }
 else
 {
  Console.WriteLine($" {food} was left untouched.");
 }
 }
}

現在定義流程處理鏈:

// The other part of the client code constructs the actual chain.
var monkey = new MonkeyHandler();
var squirrel = new SquirrelHandler();
var dog = new DogHandler();

monkey.SetNext(squirrel).SetNext(dog);

// The client should be able to send a request to any handler,not
// just the first one in the chain.
Console.WriteLine("Chain: Monkey > Squirrel > Dog\n");
ClientCode(monkey);
Console.WriteLine();

Console.WriteLine("Subchain: Squirrel > Dog\n");
ClientCode(squirrel);

輸出結果為:

Chain: Monkey > Squirrel > Dog

Client: Who wants a Nut?
Squirrel: I'll eat the Nut.
Client: Who wants a Banana?
Monkey: I'll eat the Banana.
Client: Who wants a Cup of coffee?
Cup of coffee was left untouched.

Subchain: Squirrel > Dog

Client: Who wants a Nut?
Squirrel: I'll eat the Nut.
Client: Who wants a Banana?
Banana was left untouched.
Client: Who wants a Cup of coffee?
Cup of coffee was left untouched.

總結

職責鏈模式是一個很簡單的設計模式,在需要順序處理請求比如命令或查詢時,可以使用該模式。最簡單的實現方式就是每個物件引用下一個待處理的物件,可以使用一個List或者LinkList來實現。

到此這篇關於C#設計模式之職責鏈模式的文章就介紹到這了,更多相關C#設計模式之職責鏈模式內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!

參考

https://refactoring.guru/design-patterns/chain-of-responsibility

https://stackoverflow.com/questions/48851112/is-the-chain-of-responsibility-used-in-the-net-framework

www.jb51.net/article/202503.htm