設計模式(13) 職責鏈模式

行爲型模式

行爲型模式關注於應用運行過程中算法的提供和通信關係的梳理。
相比於創建型模式和結構型模式,行爲型模式包含了最多的設計模式種類,包括:

  • 職責鏈模式
  • 模板方法模式
  • 解釋器模式
  • 命令模式
  • 迭代器模式
  • 中介者模式
  • 備忘錄模式
  • 觀察者模式
  • 狀態模式
  • 策略模式
  • 訪問者模式

職責鏈模式

職責鏈模式爲了避免請求發送者與接收者耦合在一起,讓多個對象都有可能接收請求,會將這些對象連接成一條鏈,並且沿着這條鏈傳遞請求,直到有對象處理它爲止。

GOF對外觀模式的描述爲:
Avoid coupling the sender of a request to its receiver by giving morethan one object a chance to handle the request. Chain the receivingobjects and pass the request along the chain until an object handles it.
— Design Patterns : Elements of Reusable Object-Oriented Software

在日常生活中,也會遇到類似的具有一系列“工序”的場景,比如用洗衣機洗衣服,需要經過注水、洗滌、漂洗、排水等過程,但作爲使用者,我們並不需要關注這些步驟,需要做的只是把衣服放到洗衣機、加入洗滌劑、等結束後取出而已。

職責鏈模式的使用場景

  • 輸入對象需要經過一系列處理,而每個處理環節也只針對這個對象進行修改,但產出的是同一個對象,比如洗衣服要經過多個步驟,但每個步驟產出的都是衣服本身。
  • 對象本身要經過哪些處理需要在運行態動態決定,決定的因素可能取決於對象當前的某些屬性或外部策略,但爲了把輸入方和輸出方從與每個具體的處理環節的耦合關係中解脫出來,可以考慮把它們做成一條鏈,按照每個節點的後繼依次遍歷,酌情處理。
  • 需要向多個操作發送處理請求時,可以用鏈表的形式組織它們。
  • 在對象處理經常發生動態變化的情況下,藉助鏈表來動態維護處理對象。

UML類圖:

代碼示例
假設商品的價格分成內部認購價、折扣價、平價,以及郵購價格,用職責鏈模式來進行價格的計算:

public enum PurchaseType
{
    Internal, //內部認購價格
    Discount, //折扣價
    Regular, //平價
    Mail //郵購價
}

//請求對象
public class Request
{
    public double Price { get; set; }
    public PurchaseType Type { get; set; }
    public Request(double price, PurchaseType type)
    {
        this.Price = price;
        this.Type = type;
    }
}

//抽象的操作對象
public interface IHandler
{
    void HandleRequest(Request request);
    IHandler Next { get; set; }
    PurchaseType Type { get; set; }
}

public abstract class HandlerBase : IHandler
{
    public IHandler Successor { get; set; }
    public PurchaseType Type { get; set; }
    public HandlerBase(PurchaseType type, IHandler successor)
    {
        this.Type = type;
        this.Successor = successor;
    }

    public HandlerBase(PurchaseType type) : this(type, null) { }

    //需要具體IHandler類型處理的內容
    public abstract void Process(Request request);
    //在當前結點處理,還是傳遞給下一個結點
    public virtual void HandleRequest(Request request)
    {
        if (request == null) return;
        if (request.Type == Type)
        {
            Process(request);
        }
        else if (Successor != null)
        {
            Successor.HandleRequest(request);
        }
    }

    public class InternalHandler : HandlerBase
    {
        public InternalHandler() : base(PurchaseType.Internal) { }
        public override void Process(Request request)
        {
            request.Price *= 0.6;
        }
    }

    public class MailHandler : HandlerBase
    {
        public MailHandler() : base(PurchaseType.Mail) { }
        public override void Process(Request request)
        {
            request.Price *= 1.3;
        }
    }

    public class DiscountHandler : HandlerBase
    {
        public DiscountHandler() : base(PurchaseType.Discount) { }
        public override void Process(Request request)
        {
            request.Price *= 0.9;
        }
    }

    public class RegularHandler : HandlerBase
    {
        public RegularHandler() : base(PurchaseType.Regular) { }
        public override void Process(Request request) { }
    }
}

組裝職責鏈並調用

static void Main(string[] args)
{
    IHandler handler1 = new InternalHandler();
    IHandler handler2 = new DiscountHandler();
    IHandler handler3 = new MailHandler();
    IHandler handler4 = new RegularHandler();

    handler1.Next = handler3;
    handler3.Next = handler2;
    handler2.Next = handler4;

    IHandler head = handler1;
    Request request = new Request(20, PurchaseType.Mail);
    head.HandleRequest(request);
    Console.Write(request.Price); //26

    //將MailHandler短路
    handler1.Next = handler1.Next.Next;
    request = new Request(20, PurchaseType.Mail);
    head.HandleRequest(request);
    Console.Write(request.Price); //20

}

在實際應用中,組裝職責鏈的過程可以交給創建型模式,或者從配置讀取。

職責鏈模式的特點

優勢
從上面的示例可以發現這種模式的一些優勢:

  • 降低了請求發送者和接收者之間的耦合,請求發送者只需要拿到調用鏈的頭部,就可以觸發鏈式處理。
  • 可以動態地改變鏈內節點的次序,也可以方便地動態增加、刪除節點,並即刻生效。

缺點

  • 不能保證請求一定被接收。
  • 系統性能將受到一定影響,而且在進行代碼調試時不太方便。
  • 如果調用鏈組裝不合理,可能會造成循環調用。

參考書籍:
王翔著 《設計模式——基於C#的工程化實現及擴展》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章