Design pattern trong Java: Command

Khái niệm

Command là một behavioral design pattern biến một yêu cầu thành một đối tượng độc lập chứa tất cả thông tin về yêu cầu. Việc chuyển đổi này cho phép bạn chuyển các yêu cầu dưới dạng đối số của phương thức, trì hoãn hoặc xếp hàng đợi việc thực thi một yêu cầu và hỗ trợ các hoạt động hoàn tác.

Bài toán?

Hãy tưởng tượng rằng bạn đang làm việc trên một ứng dụng soạn thảo văn bản mới. Nhiệm vụ hiện tại của bạn là tạo một thanh công cụ với một loạt các nút cho các thao tác khác nhau của trình soạn thảo. Bạn đã tạo một class Button rất gọn gàng có thể được sử dụng cho các nút trên thanh công cụ, cũng như cho các nút chung trong các hộp thoại khác nhau.

Tất cả các Button của ứng dụng đều cùng một class

Mặc dù tất cả các nút này trông giống nhau, nhưng tất cả chúng đều phải làm những việc khác nhau. Bạn sẽ đặt code ở đâu cho các trình xử lý nhấp chuột khác nhau của các nút này? Giải pháp đơn giản nhất là tạo hàng tấn class con cho mỗi nơi sử dụng nút. Các class con này sẽ chứa code phải được thực thi khi nhấp vào nút.

Không lâu sau, bạn nhận ra rằng cách làm này rất thiếu sót. Đầu tiên, bạn có một số lượng lớn các class con và điều đó sẽ ổn nếu bạn không mạo hiểm thay đổi code các class con này mỗi khi bạn sửa đổi class Button cha. 

Một số class thực hiện cùng một chức năng

Giải pháp

Thiết kế phần mềm tốt thường dựa trên nguyên tắc “separation of concerns“, điều này thường dẫn đến việc chia ứng dụng thành nhiều class.

Ví dụ phổ biến nhất: một class cho interface người dùng và một class khác cho logic nghiệp vụ. Class GUI chịu trách nhiệm hiển thị hình ảnh đẹp trên màn hình, thu nhận bất kỳ đầu vào nào và hiển thị kết quả về những gì người dùng và ứng dụng đang làm. Tuy nhiên, khi cần làm điều gì đó quan trọng, chẳng hạn như tính toán quỹ đạo của mặt trăng hoặc soạn báo cáo hàng năm, lớp GUI sẽ ủy quyền công việc cho lớp logic nghiệp vụ cơ bản.

GUI objects có thể truy cập trực tiếp vào các đối tượng logic nghiệp vụ

Command pattern gợi ý rằng các đối tượng GUI không nên gửi trực tiếp các yêu cầu này. Thay vào đó, bạn nên trích xuất tất cả các chi tiết yêu cầu, chẳng hạn như đối tượng được gọi, tên của phương thức và danh sách các đối số vào một lớp lệnh riêng biệt với một phương thức kích hoạt yêu cầu này.

Các command object đóng vai trò là liên kết giữa các đối tượng GUI và logic nghiệp vụ khác nhau. Từ bây giờ, đối tượng GUI không cần biết đối tượng logic nghiệp vụ nào sẽ nhận được yêu cầu và cách xử lý yêu cầu. Đối tượng GUI chỉ kích hoạt lệnh, lệnh này sẽ xử lý tất cả các chi tiết.

Truy cập class logic nghiệp vụ thông qua command

Bước tiếp theo là làm cho các command của bạn triển khai cùng một interface. Thông thường nó chỉ có một phương thức thực thi duy nhất mà không cần tham số. Interface này cho phép bạn sử dụng các command khác nhau với cùng một người gửi yêu cầu mà không cần kết hợp nó với các class command cụ thể. Như một phần thưởng, giờ đây bạn có thể chuyển đổi các command object được liên kết với người gửi, thay đổi hiệu quả hành vi của người gửi trong thời gian chạy.

Bạn có thể nhận thấy một phần còn thiếu của câu đố, đó là các thông số yêu cầu. Một đối tượng GUI có thể đã cung cấp cho đối tượng lớp nghiệp vụ một số tham số. Vì phương thức thực thi command không có bất kỳ tham số nào, chúng ta sẽ chuyển các chi tiết yêu cầu đến người nhận như thế nào? Hóa ra command phải được cấu hình trước với dữ liệu này hoặc có khả năng tự lấy nó.

GUI objects uỷ quyền công việc cho các command

Hãy quay lại trình soạn thảo văn bản. Sau khi áp dụng Command pattern, chúng ta không còn cần đến tất cả các class con của button đó để thực hiện các hành vi nhấp chuột khác nhau. Chỉ cần đặt một field vào class Button cha là đủ để lưu trữ tham chiếu đến command object và làm cho nút thực hiện lệnh đó khi nhấp chuột.

Kiến trúc

  1. Sender class (Invoker) chịu trách nhiệm khởi tạo các yêu cầu. Class này phải có một trường để lưu trữ một tham chiếu đến một command object. Người gửi kích hoạt command đó thay vì gửi yêu cầu trực tiếp đến người nhận. Lưu ý rằng người gửi không chịu trách nhiệm tạo đối tượng lệnh. Thông thường, nó nhận một command được tạo trước từ client thông qua phương thức khởi tạo.
  2. Command interface thường chỉ khai báo một phương thức duy nhất để thực hiện lệnh.
  3. Concrete Command thực hiện nhiều loại yêu cầu khác nhau. Một concrete command không được cho phép tự thực hiện công việc, mà là để chuyển lệnh gọi đến một trong các object logic nghiệp vụ.
    Các tham số cần thiết để thực thi một phương thức trên một đối tượng nhận có thể được khai báo dưới dạng các trường trong lệnh cụ thể. Bạn có thể làm cho các đối tượng lệnh trở nên bất biến bằng cách chỉ cho phép khởi tạo các trường này thông qua phương thức khởi tạo.
  4. Receiver class chứa một số logic nghiệp vụ. Hầu như bất kỳ đối tượng nào cũng có thể hoạt động như một máy thu. Hầu hết các lệnh chỉ xử lý các chi tiết về cách một yêu cầu được chuyển đến người nhận, trong khi người nhận tự thực hiện công việc thực tế.
  5. Client tạo và cấu hình các object command cụ thể. Client phải chuyển tất cả các tham số yêu cầu, bao gồm cả phiên bản máy thu, vào phương thức khởi tạo của lệnh. Sau đó, lệnh kết quả có thể được liên kết với một hoặc nhiều người gửi.

Áp dụng khi nào?

Sử dụng Command design pattern khi bạn muốn tham số hoá các đối tượng bằng các phép toán.

Sử dụng Command pattern khi bạn muốn xếp hàng đợi các hoạt động, lên lịch thực thi hoặc thực thi chúng từ xa.

Sử dụng Command pattern khi bạn muốn triển khai các hoạt động có thể đảo ngược.

Cách triển khai

  1. Khai báo command interface với một phương thức thực thi duy nhất.
  2. Bắt đầu trích xuất các yêu cầu vào các command class. Mỗi class phải có một tập hợp các trường để lưu trữ các đối số yêu cầu cùng với một tham chiếu đến đối tượng nhận thực tế. Tất cả các giá trị này phải được khởi tạo thông qua phương thức khởi tạo của lệnh.
  3. Xác định các class sẽ đóng vai trò là người gửi. Thêm các trường để lưu trữ lệnh vào các class này. Người gửi chỉ nên giao tiếp với các command của họ thông qua command interface. Người gửi thường không tự tạo đối tượng lệnh mà lấy chúng từ mã khách hàng.
  4. Thay đổi người gửi để họ thực hiện lệnh thay vì gửi yêu cầu trực tiếp đến người nhận.
  5. Client nên khởi tạo các đối tượng theo thứ tự sau:
    1. Tạo receivers
    2. Tạo commands và liên kết chúng với receivers nếu cần.
    3. Tạo người gửi và liên kết chúng với command cụ thể.

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

  • Single Responsibility Principle. Bạn có thể tách các class gọi các thao tác từ các class thực hiện các thao tác này.
  • Open/Closed Principle. Bạn có thể đưa các command mới vào ứng dụng mà không cần phá vỡ code ứng dụng hiện có.
  • Bạn có thể thực hiện hoàn tác / làm lại.
  • Bạn có thể triển khai thực hiện hoãn lại các hoạt động.
  • Bạn có thể tập hợp một tập hợp các lệnh đơn giản thành một tập hợp các lệnh phức tạp.
  • Code có thể trở nên phức tạp hơn vì bạn đang giới thiệu một class hoàn toàn mới giữa người gửi và người nhận.

Demo

Các bạn có thể tham khảo demo Design pattern Command ở đây nhé

Command pattern là một pattern khá là khó hiểu cho những bạn nào mới tiếp xúc với các pattern. Hi vọng bài này giúp các bạn có thể hiểu hơn thêm một loại pattern nữa. Nếu có gì không hiểu thì đừng ngại comment phía dưới nhé. Chúc các bạn có ngày học tập hiệu quả, và đừng quên tham gia Group để trao đổi kiến thức nhiều hơn nhé.

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x