close up photo of programming of codes

codecungnhau.com

Một trang web về kỹ thuật lập trình

Design Patterns: Chain of Responsibility

Mục tiêu

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 một chuỗi các trình xử lý. Khi nhận được yêu cầu, mỗi trình xử lý sẽ quyết định xử lý yêu cầu 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 đặt hàng trực tuyến. Bạn muốn hạn chế quyền truy cập vào hệ thống để chỉ những người dùng được xác thực mới có thể tạo đơn hàng. Ngoài ra, người dùng có quyền quản trị phải có toàn quyền truy cập vào tất cả các đơn đặt hàng.

Sau một chút lập kế hoạch, bạn nhận ra rằng các 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 người dùng vào hệ thống bất cứ khi nào ứng dụng nhận được yêu cầu chứa thông tin đăng nhập của người dùng. Tuy nhiên, nếu những thông tin xác thực đó không chính xác và xác thực không thành công thì không có lý do gì để tiếp tục với bất kỳ kiểm tra nào khác.

Yêu cầu phải vượt qua một loạt kiểm tra trước khi hệ thống đặt hàng tự xử lý.

Trong vài tháng tới, bạn đã triển khai thêm một vài kiểm tra tuần tự đó.

  • Một trong những đồng nghiệp của bạn cho rằng không an toàn khi chuyển dữ liệu thô thẳng đến hệ thống đặt hàng. Vì vậy, bạn đã thêm một bước xác thực bổ sung để làm sạch dữ liệu trong một yêu cầu (request).
  • Sau đó, một số người nhận thấy rằng hệ thống này 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 để lọc các yêu cầu không thành công lặp lại đến từ cùng một địa chỉ IP.
  • Ai đó khác đã gợi ý rằng bạn có thể tăng tốc hệ thống bằng cách trả về kết quả được lưu trong bộ nhớ cache cho các yêu cầu lặp lại chứa cùng một dữ liệu. Do đó, bạn đã thêm một kiểm tra khác để chỉ cho phép yêu cầu chuyển đến hệ thống nếu không có phản hồi phù hợp được lưu trong bộ nhớ cache.
Mã càng lớn, nó càng lộn xộn.

Mã của các kiểm tra, vốn đã trô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 mỗi tính năng mới. Việc thay đổi một kiểm tra đôi khi ảnh hưởng đến những kiểm tra khác. Tệ nhất là 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 để bảo trì. Bạn đã vật lộn với mã trong một thời gian, cho đến một ngày bạn quyết định cấu trúc lại toàn bộ.

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à trình xử lý (handler). Trong trường hợp của chúng ta, mỗi kiểm tra nên được trích xuất thành 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. Yêu cầu, cùng với dữ liệu của nó, được chuyển tới phương thức này như một đối số.

Mẫu đề nghị bạn liên kết các trình xử lý này thành một chuỗi. Mỗi trình xử lý được liên kết có một trường để lưu trữ tham chiếu đến trình xử lý tiếp theo trong chuỗi. Ngoài việc xử lý một yêu cầu, trình xử lý còn chuyển yêu cầu đó dọc theo chuỗi. Yêu cầu di chuyển dọc theo chuỗi cho đến khi tất cả các trình xử lý có cơ hội xử lý nó.

Đây là phần tốt nhất: trình xử lý có thể quyết định không chuyển yêu cầu xuống chuỗi tiếp theo và dừng bất kỳ quá trình xử lý nào một cách hiệu quả.

Trong ví dụ với các hệ thống đặt hàng, một trình xử lý thực hiện xử lý và sau đó quyết định có chuyển yêu cầu xuống chuỗi tiếp tục hay không. Giả sử yêu cầu chứa đúng dữ liệu, tất cả các trình xử lý có thể thực hiện hành vi chính của nó, cho dù đó là kiểm tra xác thực hay lưu vào bộ nhớ đệm.

Những trình xử lý được xếp từng hàng một, tạo thành một chuỗi.

Tuy nhiên, có một cách tiếp cận hơi khác (và nó chuẩn hơn một chút), trong đó, khi nhận được yêu cầu, trình xử lý sẽ quyết định xem có thể xử lý yêu cầu đó hay không. Nếu có thể, nó sẽ không truyền yêu cầu đó nữa. Vì vậy, hoặc chỉ có một trình xử lý xử lý yêu cầu hoặc không có trình xử lý nào cả. Cách tiếp cận này rất phổ biến khi xử lý các sự kiện của ngăn xếp các phần tử trong giao diện người dùng đồ họa.

Ví dụ: khi người dùng nhấp vào một button, sự kiện sẽ lan truyền qua chuỗi các phần tử GUI bắt đầu với button, đi dọc theo các container của nó (như form hoặc panel) và kết thúc với cửa sổ ứng dụng chính. Sự kiện được xử lý bởi phần tử đầu tiên trong chuỗi có khả năng xử lý sự kiệ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.

Một chuỗi có thể được hình thành từ một nhánh của cây đối tượng.

Điều quan trọng là tất cả các lớp trình xử lý phải triển khai cùng một giao diện. Mỗi trình xử lý cụ thể chỉ tuân theo một phương thức execute. Bằng cách này, bạn có thể soạn chuỗi trong thời gian thực thi, sử dụng nhiều trình xử lý 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.

Cấu trúc

  1. Giao diện Handler dùng chung cho tất cả các trình xử lý cụ thể. Nó thường chỉ chứa một phương thức duy nhất để xử lý các yêu cầu, nhưng đôi khi nó cũng có thể có một phương thức khác để thiết lập trình xử lý tiếp theo trên chuỗi.
  2. Trình xử lý cơ sở BaseHandler là một lớp tùy chọn nơi bạn có thể đặt mã soạn sẵn chung cho tất cả các lớp trình xử lý cụ thể.
    Thông thường, lớp này định nghĩa một trường để lưu trữ một tham chiếu đến trình xử lý tiếp theo. Các client có thể xây dựng một chuỗi bằng cách truyền một trình xử lý cho phương thức khởi tạo hoặc thiết lập của trình xử lý trước. Lớp cũng có thể thực hiện hành vi xử lý mặc định: nó có thể truyền việc thực thi cho trình xử lý tiếp theo sau khi kiểm tra sự tồn tại của trình xử lý tiếp theo.
  3. Các Concrete Handler chứa mã thực tế để xử lý các yêu cầu. Khi nhận được một yêu cầu, mỗi trình xử lý phải quyết định xem có xử lý nó hay không và thêm vào đó, có chuyển nó theo chuỗi hay không.
    Các trình xử lý thường độc lập và không thay đổi, chấp nhận tất cả dữ liệu cần thiết chỉ một lần thông qua hàm tạo.
  4. Client có thể soạn chuỗi chỉ một lần hoặc soạn chúng động, tùy thuộc vào logic của ứng dụng. Lưu ý rằng một yêu cầu có thể được gửi đến bất kỳ trình xử lý nào trong chuỗi – không nhất thiết phải là cái đầu tiên.

Khả năng áp dụng

  • Sử dụng mẫu khi chương trình của bạn dự kiến sẽ xử lý các loại yêu cầu khác nhau theo nhiều cách khác nhau, nhưng chưa biết trước loại yêu cầu chính xác và trình tự của chúng.
  • Sử dụng mẫu khi cần thực thi một số trình xử lý theo một thứ tự cụ thể.
  • Sử dụng mẫu khi tập hợp các trình xử lý và thứ tự của chúng được cho là thay đổi trong thời gian thực thi.

Ưu và nhược điểm

😄😄😄

Bạn có thể kiểm soát thứ tự xử lý yêu cầu.

Nguyên tắc Đơn Trách nhiệm. Bạn có thể tách các lớp gọi các thao tác từ các lớp thực hiện các thao tác.

Nguyên tắc Mở / Đóng. Bạn có thể giới thiệu các trình xử lý mới vào ứng dụng mà không vi phạm mã client hiện có.

🙁🙁🙁

Một số yêu cầu có thể không giải quyết được.

Mối quan hệ với các mẫu khác

Chain of Responsibility, Command, Mediator và Observer giải quyết các cách khác nhau để kết nối người gửi (sender) và người nhận (receiver) yêu cầu (request):

  • Chain of Responsibility chuyển một yêu cầu tuần tự dọc theo một chuỗi động gồm những người nhận tiềm năng cho đến khi một trong số đó xử lý nó.
  • Command thiết lập kết nối một chiều giữa người gửi và người nhận.
  • Mediator loại bỏ các kết nối trực tiếp giữa người gửi và người nhận, buộc chúng phải giao tiếp gián tiếp thông qua một đối tượng trung gian.
  • Observer cho phép người nhận đăng ký động và hủy đăng ký nhận yêu cầu.

Chain of Responsibility thường được sử dụng cùng 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ể truyền yêu cầu đó qua chuỗi của tất cả các thành phần cha đến 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, bạn 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 biểu diễn bằng một yêu cầu. Tuy nhiên, có một cách tiếp cận khác, trong đó bản thân yêu cầu là một đối tượng Command. Trong trường hợp này, bạn có thể thực hiện cùng một thao tác trong một loạt các ngữ cảnh khác nhau được liên kết thành một chuỗi.

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 đối tượng. Tuy nhiên, có một số khác biệt quan trọng. Các trình xử lý CoR 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 truyền yêu cầu vào bất kỳ lúc nào. Trong khi, các 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.

Chương trình tham khảo

C++

C#

Python


Đã đăng vào

trong

bởi

Bình luận

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *