Chain of Responsibility Pattern
Chain of Responsibility là một mẫu thiết kế hành vi cho phép bạn chuyển các yêu cầu dọc theo chuỗi người xử lý. Khi nhận được một request, mỗi trình xử lý quyết định xử lý request hoặc chuyển nó cho trình xử lý tiếp theo trong chuỗi.
😕 Vấn đề
Hãy tưởng tượng rằng bạn đang làm việc trên một hệ thống online order. Bạn muốn hạn chế quyền truy cập vào hệ thống để chỉ những user được xác thực mới có thể tạo order. Ngoài ra, user có quyền quản trị phải có toàn quyền truy cập vào tất cả các orders.
Sau một chút lập kế hoạch, bạn nhận ra rằng những kiểm tra này phải được thực hiện tuần tự. Ứng dụng có thể cố gắng xác thực user với hệ thống bất cứ khi nào nó nhận được request có chứa thông tin đăng nhập của user. Tuy nhiên, nếu những thông tin đăng nhập đó không chính xác và xác thực không thành công, không có lý do gì để tiến hành bất kỳ kiểm tra nào khác.
Trong vài tháng tới, bạn đã thực hiện thêm một số kiểm tra tuần tự đó.
Một trong những đồng nghiệp của bạn gợi ý rằng việc chuyển dữ liệu thô thẳng đến hệ thống order là không an toàn. Vì vậy, bạn đã thêm một bước xác thực bổ sung để validate dữ liệu trong một request.
Sau đó, ai đó nhận thấy rằng hệ thống dễ bị bẻ khóa mật khẩu . Để phủ nhận điều này, bạn đã nhanh chóng thêm một kiểm tra rằng các bộ lọc lặp lại các request không thành công đến từ cùng một địa chỉ IP.
Một người khác gợi ý rằng bạn có thể tăng tốc hệ thống bằng cách trả lại kết quả được lưu trong bộ nhớ cache trên các request lặp lại có chứa cùng một dữ liệu. Do đó, bạn đã thêm một kiểm tra khác cho phép request chuyển đến hệ thống chỉ khi không có phản hồi được lưu trong bộ nhớ cache phù hợp.
Code trông giống như một mớ hỗn độn, ngày càng trở nên cồng kềnh hơn khi bạn thêm từng tính năng mới. Thay đổi một lần kiểm tra đôi khi ảnh hưởng đến những lần kiểm tra khác. Tệ hơn hết, khi bạn cố gắng sử dụng lại các kiểm tra để bảo vệ các thành phần khác của hệ thống, bạn phải sao chép một số mã vì các thành phần đó yêu cầu một số kiểm tra, nhưng không phải tất cả chúng.
Hệ thống trở nên rất khó hiểu và tốn kém để duy trì. Bạn đã phải vật lộn với code trong một thời gian, cho đến một ngày bạn quyết định tái cấu trúc lại toàn bộ sự việc.
🙂 Giải Pháp
Giống như nhiều mẫu thiết kế hành vi khác, Chain of Responsibility dựa vào việc chuyển đổi các hành vi cụ thể thành các đối tượng độc lập được gọi là handlers. Trong trường hợp của chúng tôi, mỗi lần kiểm tra nên được trích xuất vào lớp riêng của nó bằng một phương thức duy nhất thực hiện kiểm tra. Request, cùng với dữ liệu của nó, được chuyển đến phương thức này như một đối số.
Pattern gợi ý rằng bạn liên kết những handlers này thành một chuỗi. Mỗi handler được liên kết có một trường để lưu trữ tham chiếu đến handler tiếp theo trong chuỗi. Ngoài việc xử lý một request, handler chuyển request xa hơn dọc theo chuỗi. Yêu cầu di chuyển dọc theo chuỗi cho đến khi tất cả những handler có cơ hội xử lý nó.
Đây là phần tốt nhất: một handler có thể quyết định không chuyển request xuống chuỗi và dừng bất kỳ quá trình xử lý nào nữa một cách hiệu quả.
Trong ví dụ của chúng tôi với các hệ thống order, một handler thực hiện việc xử lý và sau đó quyết định có chuyển request xa hơn xuống chuỗi hay không. Giả sử request chứa đúng dữ liệu, tất cả các handler có thể thực hiện hành vi chính của họ, cho dù đó là kiểm tra xác thực hay bộ nhớ đệm.
Tuy nhiên, có một cách tiếp cận hơi khác (và nó kinh điển hơn một chút), trong đó, khi nhận được request, handler sẽ quyết định liệu nó có thể xử lý nó hay không. Nếu có thể, nó sẽ không vượt qua request nữa. Vì vậy, chỉ có một handler xử lý request hoặc không có gì cả. Cách tiếp cận này rất phổ biến khi xử lý các sự kiện trong các ngăn xếp các yếu tố trong giao diện người dùng đồ họa.
Ví dụ, khi user click vào một button, event sẽ lan truyền qua chuỗi các phần tử GUI bắt đầu bằng button, đi dọc theo các vùng chứa của nó (như form hoặc table và kết thúc với cửa sổ ứng dụng chính. Event được xử lý bởi phần tử đầu tiên trong chuỗi có khả năng xử lý nó. Ví dụ này cũng đáng chú ý vì nó cho thấy rằng một chuỗi luôn có thể được trích xuất từ một cây đối tượng.
Điều quan trọng là tất cả các lớp xử lý đều triển khai cùng một interface. Mỗi handler cụ thể chỉ nên quan tâm đến việc sau đây có phương thức execute. Bằng cách này, bạn có thể soạn các chuỗi trong thời gian chạy, sử dụng các handler khác nhau mà không cần ghép mã của bạn với các lớp cụ thể của chúng.
⚡Khả năng ứng dụng
Sử dụng Chain of Responsibility pattern khi chương trình của bạn dự kiến sẽ xử lý các loại request khác nhau theo nhiều cách khác nhau, nhưng các loại request chính xác và trình tự của chúng chưa được biết trước đó.
Sử dụng pattern khi cần thiết để thực thi một số handler theo một thứ tự cụ thể.
Sử dụng CoR pattern khi tập hợp các handler và thứ tự của chúng được cho là thay đổi trong thời gian chạy.