Design Pattern | Command Pattern

Command Pattern đóng gói yêu cầu như một đối tượng của chính nó, thông thường, khi một đối tượng đưa ra yêu cầu cho một đối tượng thứ hai thực hiện hành động, đối tượng thứ nhất sẽ gọi một phương thức của đối tượng thứ hai và đối tượng thứ hai hoàn thành nhiệm vụ. Trong tình huốn này, đối tượng người gửi (sender) trực tiếp phải giao tiếp với đối tượng người nhận. Thay vì để các đối tượng này giao tiếp trực tiếp với nhau, Command Pattern tạo một đối tượng command ở giữa người gửi và người nhận.


Bằng cách này, người gửi không cần biết về người nhận và các phương thức cần phải gọi. Hãy nghĩ về nó khi các ông chủ trong công ty sử dụng các schedule để thực hiện các nhiệm vụ, ví dụ, nếu sếp cần một công nhân lên lịch họp và cũng cần một công nhân khác nói chuyện với khách hàng về kinh doanh, sếp có thể sử dụng các mentos (bản ghi nhớ). Nhưng là một người rất bận rộn, Sếp không có thời gian để nhớ công nhân  nào phải làm gì và sẽ không có thời gian đi bộ đến các công nhân khác nhau để yêu cầu họ hoàn thành nhiệm vụ. Thay vào đó, ông chủ chỉ có thể viết các nhiệm vụ vào một tờ giấy và một thư ký có thể sắp xếp để nhân viên hoàn thành chúng. Sếp đang gói các lệnh vào tờ giấy.

Tương tự, trong phần mềm, một đối tượng sender (người gửi) tạo ra một đối tượng command (lệnh). Nhưng điều gì thực sự làm cho đối tượng command thực hiện những gì nó phải làm và gọi đối tượng người nhận cụ thể để hoàn thành nhiệm vụ ? Đây là nơi một Invoke đến. Mẫu lệnh có một đối tượng khác để hoàn thành bất kỳ nhiệm vụ nào cần thực hiện, được gọi là Invoke. Một trình xử lý lệnh cũng có thể được sử dụng để cơ bản theo dõi các lệnh, thao tác với chúng và gọi chúng. Hãy nghĩ về Invoke giống như thư ký trong ví dụ về công ti, theo dõi các ghi nhớ, và đảm bảo ghi nhớ được thực hiện đúng lúc. 

Vậy bạn có thể dùng Command Pattern trong phần mềm của bạn ở đâu ? Có nhiều mục đích khác nhau đẻ sử dụng Command Pattern, một mục đích của sử dụng mẫu lệnh là lưu trữ (store) và lên lịch (schedule) các yêu cầu khác nhau. Khi một đối tượng gọi một phương thức của đối tượng khác, bạn thực sự không thể làm gì với các call phương thức đó. Biến các yêu cầu khác nhau trong phần mềm của bạn thành các đối tượng command có thể cho phép bạn coi chúng như cách bạn đối xử với các đối tượng khác. Bạn có thể lưu trữ các đối tượng này vào danh sách, bạn có thể thao tác chúng trước khi hoàn thành hoặc bạn có thể đặt chúng vào hàng đợi để bạn có thể lên lịch các lệnh khác nhau để hoàn thành vào các thời điểm khác nhau. 

Ví dụ, bạn có thể sử dụng Command Pattern để có thể có chuông báo thức trong một phần mềm lịch. Khi một sự kiện được tạo ra trong lịch, một đối tượng lệnh có thể được tạo để rung chuông báo động. Lệnh này có thể được đưa vào hàng đợi, để lệnh có thể được hoàn thành sau khi sự kiện thực sự được lên lịch. Một mục đích quan trọng khác của Command Pattern là cho phép các lệnh được hoàn tác và làm lại (undo/redo). Giống như cách bạn có thể hoàn tác các chỉnh sửa trong tài liệu, ví dụ: sẽ dễ dàng hơn nếu bằng cách sử dụng sơ đồ, giả sử bạn đang tạo phần mềm chỉnh sửa văn bản, có thể có nhiều command (lệnh) khác nhau được thực thi trong phần mềm chỉnh sửa văn bản của bạn như xóa văn bản, thay đổi phông chữ, thay đổi pixel, v.v. 

Để đạt được chức năng undo/redo, phần mềm của bạn sẽ cần hai danh sách, danh sách lịch sử chứa tất cả các lệnh đã được thực thi, và danh sách redo được sử dụng để đặt các lệnh sẽ được hoàn tác. Mỗi khi lệnh được yêu cầu, một đối tượng lệnh được tạo và thực thi. Mỗi khi lệnh được hoàn thành, lệnh đó được đi vào danh sách lịch sử (history list). 


Bây giờ, nếu người dùng muốn hoàn tác (undo) lại một lệnh thì sao ? Chà, phần mềm sẽ nhìn vào danh sách lịch sử và xem lệnh gần đây nhất được thực thi, phần mềm sẽ yêu cầu lệnh này tự động hoàn tác và sau đó đưa nó vào danh sách làm lại (redo list). Nếu người dùng cần redo, phần mềm sẽ thực hiện lệnh gần đây nhất được redo trong list redo, và yêu cầu thực hiện lại, sau đó di chuyển nó vào danh sách lịch sử một lần nữa. Chức năng undo/redo không chỉ cần thiết trong phần mềm chỉnh sửa văn bản, nó còn có thể cần thiết trong bất kỳ ứng dụng nào, điều quan trong là Command Pattern cho phép bạn thực hiện những điều yêu cầu mà bạn không thể thực hiện nếu chúng là các call phương thức đơn giản từ đối tượng này sang đối tượng khác.\


Trên là sơ đồ UML của Command Pattern. Bạn có một superclass Command và tất cả các lệnh là thể hiện của các lớp con của superclass này. Superclass định nghĩa các hành vi phổ biến của các lệnh của bạn. Mỗi lệnh sẽ có các phương thức thực thi (uxecute()), không thực thi và isReverible(). Phương thức thực thi sẽ thực hiện công việc mà lệnh phải làm, không thực thi sẽ thực hiện công việc hoàn tác lệnh, và isReverible() xác định nếu lệnh có thể đảo ngược, trả về True nếu lệnh có thể được hoàn tác. Có thể có một số lệnh không thể được hoàn tác, ví dụ bạn không thể undo một văn bản đã được saved, các lớp lệnh cụ thể này sẽ gọi các lớp nhận cụ thể để xử lý các công việc thực tế của việc hoàn thành lệnh.

Nếu ta xem xét chi tiết hơn về lớp lệnh cụ thể (concrete command class), ví dụ như pasting text, chúng ta có thể nhìn thấy một đối tượng lệnh sẽ trông như thế nào. 


Chúng ta có thể nhìn thấy rằng Pasteommand mở rộng của Command. Đối tượng PasteCommand cũng theo dõi nơi văn bản sẽ được chèn và những gì sẽ được chèn. Đây là một khía cạnh rất quan trọng của đối tượng lệnh. Nó phải theo dõi chi tiết về trạng thái hiện tại cảu tài liệu để các lệnh có thể đảo ngược. Trong trường hợp này, PasteCommand cũng là một lệnh có thể đảo ngược, vì vậy isReversible() là true, Khi các phương thức excute() và unexcute() được gọi đây là nơi đối tượng lệnh thực sự gọi trên trên receiver, trong trường hợp này là tài liệu để thực sự hoàn thành công việc, mã nguồn cho Invoker được thực hiện trong các bước đơn giản:


Đầu tiên, nó cần tham chiếu đến CommandManager, là đối tượng quản lý danh sách history và redo list. Invoker sau đó tạo đối tượng command với thông tin cần thiết để hoàn thành lệnh, sau đó gọi trình quản lý để thực thi lệnh. 

Có nhiều lợi ích đến từ việc ửu dụng Command Pattern. Như tôi đã đề cập trước đây, nó cho phép bạn thao tác các lệnh như các đối tượng theo cách bạn không thể thực hiện với các lần gọi phương thức. Làm việc với các đối tượng lệnh cho phép bạn thêm các chức năng như tôi đã đề cập trước đó, là đưa các lệnh vào hàng đợi và thêm chức năng undo/redo. Một lợi ích chính khác của mẫu lệnh là nó tách các đối tượng của chương trình phần mềm của bạn. Khi bạn có một lớp muốn đưa ra yêu cầu, lớp đó không cần biết về các đối tượng khác trong hệ thống phần mềm, nó có thể chỉ đơn giản là tạo đối tượng lệnh và để đối tượng lệnh xử lý công việc bằng cách gọi các đối tượng nhận, giống như tôi ví dụ về ông Sếp trước đó. 

Đối tượng không cần biết ai giải quyết yêu cầu nữa, mẫu lệnh cũng cho phép bạn rút ra logic từ giao diện người dùng của mình, thông thường, các yêu cầu xử lý mã được đưa vào bộ xử lý sự kiện của giao diện người dùng, Tuy nhiên, thật không có ý nghĩa khi nhiều logic ứng dụng ngồi trong các lớp giao diện người dùng của bạn, các lớp giao diện người dùng chỉ nên xử lý các vấn đề giao diện người dùng như nhận thông tin đến. 

Thay vào đó, Command Pattern tạo ra một lớp mới, nơi các đối tượng lênh này sẽ đi. Mỗi lần nhấn nút, nó sẽ tạo ra một đối tượng lệnh thay thế, và đó là nơi logic sẽ được thiết lập, các đối tượng lệnh này độc lập với giao diện người dùng của bạn. Vì vậy, nó giúp việc thêm các thay đổi như các nút mới vào giao diện người dùng phần mềm của bạn được dễ dàng hơn và nhanh hơn. Bạn có thể biến từng dịch vụ trong hệ thống của mình thành đối tượng của riêng mình và cho phép chức năng linh hoạt hơn nhiều. Lần tới khi bạn đang tạo một ứng dụng, hãy thử áp dụng Command Pattern, bạn sẽ sớm thấy rằng việc sử dụng pattern này có thể làm cho các chương trình phần mềm của bạn linh hoạt và dễ bảo trì hơn, và bạn sẽ luôn sử dụng nó trên mọi phần mềm bạn tạo sau này. Chúc may mắn.
Cảm ơn.

Nhận xét

Bài đăng phổ biến từ blog này

Hiểu về Norm Regularization

Những thuật toán nền tảng trong lĩnh vực Trí tuệ nhân tạo (Artificial Intelligence I)