Tổng quan về Chain of Responsibility Design Pattern


Chain of Responsibility Pattern cho phép một đối tượng gửi một request nhưng không biết đối tượng nào sẽ nhận và xử lý nó. Điều này được thực hiện bằng cách kết nối các đối tượng nhận request thành một chuỗi (chain) và gửi request theo chuỗi đó cho đến khi có một đối tượng xử lý nó.


I. Giới thiệu

Chain of Responsibility là một mẫu thiết kế thuộc nhóm hành vi (Behavioral Pattern).

Mục đích: cho phép một đối tượng gửi một request nhưng không biết đối tượng nào sẽ nhận và xử lý nó. Điều này được thực hiện bằng cách kết nối các đối tượng nhận request thành một chuỗi (chain) và gửi request theo chuỗi đó cho đến khi có một đối tượng xử lý nó.

Chain of Responsibility Pattern hoạt động như một danh sách liên kết (Linked list) với việc đệ quy duyệt qua các phần tử (recursive traversal).

Mẫu này gắn liền với nhiều hệ thống xử lý yêu cầu. Ví dụ tổng đài hay bất kì quy trình làm việc nào cũng sẽ đi theo tư tưởng của Chain of Responsibility. Có lỗi thì sẽ chọn đưa cho người có liên quan tiếp theo hoặc chấm dứt yêu cầu tại điểm đó.

II. Kiến trúc

Các thành phần trong mô hình:

  • Client: tạo ra các yêu cầu và yêu cầu đó sẽ được gửi đến các đối tượng tiếp nhận.
  • IHandler: định nghĩa 1 interface để xử lý các yêu cầu. Gán giá trị cho đối tượng successor (không bắt buộc).
  • Handler (HandleFirst, Second, Third,...): xử lý yêu cầu. Có thể truy cập đối tượng successor (thuộc class Handler). Nếu đối tượng Handler không thể xử lý được yêu cầu, nó sẽ gởi lời yêu cầu cho successor của nó.

Cách hoạt động:

  • Client gọi hàm handle của Handler đầu Chain , truyền vào nội dung yêu cầu / tham số yêu cầu.
  • Handler đầu tiên xác định yêu cầu có xử lý được không. Nếu không xử lý được, handler gọi handler tiếp theo (nếu có).

III. Source code minh họa với C#

Trong ví dụ này, chúng ta sẽ tự xây dựng hệ thống kho lưu trữ hàng hóa.

  • Hệ thống gồm 3 kho Mini, Medium và Big storage. Mọi đơn đặt hàng sẽ được kho Mini xử lý.
  • Nếu số lượng order > max của kho Mini (giả định là 10) thì kho Mini sẽ chuyển đơn hàng sang cho kho Medium.
  • Tương tự, nếu số lượng order > max của kho Medium (giả định là 10) thì kho Medium sẽ chuyển đơn hàng sang cho kho Big. 

Các bước cài đặt:

  • Xác định interface của Handler
    Quan trọng nhất là hàm Handle. Có thể truyền các biến cần thiết để xử lý yêu cầu. Nếu các biến quá dài, có thể đóng gói chúng thành 1 struct / class. Ngoài ra, có thể thêm các hàm quản lý như SetHandler, …
    interface IHandler
    {
        IHandler Successor { get; set; }

        void RequestOrder(int amount);
    }

 

  • Cài đặt Handler
    Để tránh lặp code, một số hàm boilerplate như SetHandler, CallNextHandler, ... có thể cài đặt ở lớp abstract class BaseHandler. Ngoài ra, có thể cài đặt default behaviour cho hàm Handle và các hàm khác nếu muốn. Ngoài ra, tùy nhu cầu sửa Chain, có thể cho biến NextHandler thay đổi được hoặc không.
    class MiniStorage : IHandler
    {
        public IHandler Successor { get; set; }

        public void RequestOrder(int amount)
        {
            if (amount < 10)
            {
                Console.WriteLine($"Mini storage: I can handle less than 10 quantity. DONE!");
            }
            else
            {
                Console.WriteLine($"Mini storage: I received the request but I can not handle more than 10 quantity. Passed to Medium storage");
                Successor?.RequestOrder(amount);
            }
        }
    }

    class MediumStorage : IHandler
    {
        public IHandler Successor { get; set; }

        public void RequestOrder(int amount)
        {
            if (amount < 50)
            {
                Console.WriteLine($"Medium storage: I can handle less than 50 quantity. DONE!");
            }
            else
            {
                Console.WriteLine($"Medium storage: I received the request but I can not handle more than 50 quantity. Passed to Big storage");
                Successor?.RequestOrder(amount);
            }
        }
    }

    class BigStorage : IHandler
    {
        public IHandler Successor { get; set; }

        public void RequestOrder(int amount)
        {
            if (amount < 100)
            {
                Console.WriteLine($"Big handler: I can handle less than 100 quantity. DONE!");
            }
            else
            {
                Console.WriteLine($"Big storage: I received the request but I can not handle more than 100 quantity. Passed to Fatory");
                Successor?.RequestOrder(amount);
            }
        }
    }

    class FactoryHandler : IHandler
    {
        public IHandler Successor { get; set; }

        public void RequestOrder(int amount)
        {
            Console.WriteLine($"Factory: I received the request. You will receice product from us");

        }
    }

 

  • Xây dựng / sử dụng Chain of Responsibility
    Client có thể nhận một Chain hoặc tạo mới một Chain. Có thể tạo Factory để xây Chain dựa trên các setting ngoài. Khi sử dụng, cần chú ý:
        - Chain có thể có 1 phần tử thôi.
        - Một số yêu cầu có thể không tới được cuối Chain.
        - Một số yêu cầu có thể tới cuối Chain không được xử lý.
        - Yêu cầu có thể bị xử lý hoặc chặn bởi bất kì Handler nào trong Chain.
    class ChainOfHandlers
    {
        readonly IHandler _mini = new MiniStorage();
        readonly IHandler _medium = new MediumStorage();
        readonly IHandler _big = new BigStorage();
        readonly IHandler _factory = new FactoryHandler();

        public ChainOfHandlers()
        {
            _mini.Successor = _medium;
            _medium.Successor = _big;
            _big.Successor = _factory;
        }

        public void Handle(int amount)
        {
            _mini.RequestOrder(amount);
        }
    }

 

  • Sử dụng
    class Program
    {
        static void Main(string[] args)
        {
            var chain = new ChainOfHandlers();
            Console.WriteLine("Enter quantity: ");
            int amount = Convert.ToInt32(Console.ReadLine());
            chain.Handle(amount);
        }
    }

 

  • Cân nhắc kết hợp với pattern khác
    Composite Thường thấy khi cài đặt UI những thứ như SetFocus, ... Có thể dùng để nhận yêu cầu từ con và truyền yêu cầu lên cha cho đến khi có thành phần nào đó chấp nhận xử lý. Command Các yêu cầu có thể dưới dạng command, cho nhiều Handler có cơ hội tiếp nhận và xử lý command. Hoặc ngược lại, các Handler có thể là command, và yêu cầu là một context object được truyền đi để Handler xử lý.

IV. Ưu & nhược điểm

    1. Ưu điểm

  • Giảm kết nối (loose coupling): Thay vì một đối tượng có khả năng xử lý yêu cầu chứa tham chiếu đến tất cả các đối tượng khác, nó chỉ cần một tham chiếu đến đối tượng tiếp theo. Tránh sự liên kết trực tiếp giữa đối tượng gửi yêu cầu (sender) và các đối tượng nhận yêu cầu (receivers).
  • Tăng tính linh hoạt : đảm bảo Open/Closed Principle
  • Phân chia trách nhiệm cho các đối tượng: đảm bảo Single Responsibility Principle
  • Có khả năng thay đổi dây chuyền (chain) trong thời gian chạy.

    2. Nhược điểm

  • Một số yêu cầu có thể không được xử lý: Trường hợp tất cả Handler đều không xử lý

V. Khi nào thì sử dụng

Dưới đây chúng ta có thể liệt kê một số trường hợp mà khi gặp sẽ phải cân nhắc sử dụng Chain of Responsibility pattern:

  • nhiều hơn một đối tượng có khả năng xử lý một yêu cầu trong khi đối tượng cụ thể nào xử lý yêu cầu đó lại phụ thuộc vào ngữ cảnh sử dụng.
  • Muốn gửi yêu cầu đến một trong số vài đối tượng nhưng không xác định đối tượng cụ thể nào sẽ xử lý yêu cầu đó.
  • Khi cần phải thực thi các trình xử lý theo một thứ tự nhất định..
  • Khi một tập hợp các đối tượng xử lý có thể thay đổi động: tập hợp các đối tượng có khả năng xử lý yêu cầu có thể không biết trước, có thể thêm bớt hay thay đổi thứ tự sau này.

VI. Design Pattern liên quan

  • Chain of Responsibility thường được sử dụng kết hợp với Composite. Trong trường hợp này, khi một thành phần lá nhận được một yêu cầu, nó có thể chuyển nó qua chuỗi của tất cả các thành phần cha xuống gốc của cây đối tượng.
  • Các trình xử lý trong Chain of Responsibility có thể được thực hiện dưới dạng Command. Trong trường hợp này, ta có thể thực thi nhiều thao tác khác nhau trên cùng một đối tượng ngữ cảnh, được thể hiện bằng một yêu cầu.
  • Chain of Responsibility và Decorator có cấu trúc lớp rất giống nhau. Cả hai mẫu đều dựa vào thành phần đệ quy để truyền việc thực thi qua một loạt các đối tượng. Tuy nhiên, có một số khác biệt quan trọng. Các trình xử lý Chain of Responsibility có thể thực hiện các hoạt động tùy ý độc lập với nhau. Chúng cũng có thể ngừng chuyển yêu cầu vào bất kỳ lúc nào. Mặt khác, các trình Decorator khác nhau có thể mở rộng hành vi của đối tượng trong khi vẫn giữ cho nó nhất quán với giao diện cơ sở. Ngoài ra, Decorator không được phép phá vỡ quy trình của yêu cầu.

 

Tài liệu tham khảo

[1] Refactoring.Guru

[2] Design Patterns for Dummies, Steve Holzner, PhD

[3] Head First, Eric Freeman

[4] Gang of Four Design Patterns 4.0

[5] Dive into Design Pattern

Chia sẽ bài viết :