close up photo of programming of codes

codecungnhau.com

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

Design Patterns: Command

Mục tiêu

Command là một mẫu thiết kế hành vi 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. Sự chuyển đổi này cho phép bạn tham số hóa các phương thức với các yêu cầu khác nhau, 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.

Vấ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 lớp 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 nút của ứng dụng đều có nguồn gốc từ cùng một lớp.

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 mã ở đâ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 lớp con cho mỗi nơi mà nút được sử dụng. Các lớp con này sẽ chứa mã phải được thực thi khi nhấp vào nút.

Rất nhiều lớp con Button. Sai lầm có thể có là gì?

Không lâu sau, bạn nhận ra rằng cách tiếp cận này rất thiếu sót. Đầu tiên, bạn có một số lượng lớn các lớp con và điều đó sẽ ổn nếu bạn không mạo hiểm phá mã trong các lớp con này mỗi khi bạn sửa đổi lớp Button cơ sở. Nói một cách đơn giản, mã GUI của bạn đã trở nên phụ thuộc một cách khó hiểu vào mã logic nghiệp vụ dễ thay đổi.

Một số lớp thực hiện cùng một chức năng.

Và đây là phần xấu xí nhất. Một số thao tác, chẳng hạn như sao chép / dán văn bản, sẽ cần được gọi từ nhiều nơi. Ví dụ: người dùng có thể nhấp vào nút “Copy” nhỏ trên thanh công cụ hoặc sao chép nội dung nào đó qua menu ngữ cảnh hoặc chỉ cần nhấn Ctrl + C trên bàn phím.

Ban đầu, khi ứng dụng chỉ có thanh công cụ, có thể đặt việc triển khai các hoạt động khác nhau vào các lớp con của Button. Nói cách khác, có mã để sao chép văn bản bên trong lớp con CopyButton là tốt. Nhưng sau đó, khi bạn triển khai menu ngữ cảnh, lối tắt và những thứ khác, bạn phải sao chép mã của thao tác trong nhiều lớp hoặc làm cho menu phụ thuộc vào các nút, đó là một lựa chọn thậm chí còn tệ hơn.

Giải pháp

Thiết kế phần mềm tốt thường dựa trên nguyên tắc tách biệt các mối quan tâm, điều này thường dẫn đến việc chia ứng dụng thành nhiều lớp. Ví dụ phổ biến nhất: một lớp cho giao diện người dùng đồ họa và một lớp khác cho logic nghiệp vụ. Lớp 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.

Trong mã, nó có thể trông như thế này: một đối tượng GUI gọi một phương thức của đối tượng logic nghiệp vụ, truyền cho nó các đối số. Quá trình này thường được mô tả là một đối tượng gửi một yêu cầu khác.

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

Mẫu Command 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 Command riêng biệt với một phương thức kích hoạt yêu cầu này.

Các đối tượng Command đó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 xử lý tất cả các chi tiết.

Truy cập lớp logic nghiệp vụ thông qua một Command.

Bước tiếp theo là làm cho các lệnh (command) của bạn triển khai cùng một giao diện. 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ố. Giao diện này cho phép bạn sử dụng các lệnh khác nhau với cùng một người gửi yêu cầu (sender) mà không cần kết hợp nó với các lớp lệnh cụ thể. Giờ đây bạn có thể chuyển đổi các đối tượng lệnh đượ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 thực thi.

Bạn có thể thấy một phần còn thiếu của yêu cầu, đó là các thông số. 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 lệnh không có bất kỳ tham số nào, vây ta sẽ chuyển thông tin chi tiết yêu cầu đến người nhận như thế nào? Hóa ra lệnh phải được cấu hình trước với dữ liệu này hoặc có thể tự lấy nó.

Các đối tượng GUI ủy 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 mẫu Command, ta không cần tất cả các lớp con button đó để thực hiện các hành vi nhấp chuột khác nhau. Chỉ cần đặt một trường vào lớp Button cơ sở là đủ để lưu trữ một tham chiếu đến một đối tượng lệnh và làm cho nút thực hiện lệnh đó khi nhấp chuột.

Ta sẽ triển khai một loạt các lớp lệnh cho mọi hoạt động có thể và liên kết chúng với các nút cụ thể, tùy thuộc vào hành vi dự kiến ​​của các nút.

Các phần tử GUI khác, chẳng hạn như menu, lối tắt hoặc toàn bộ hộp thoại, có thể được thực hiện theo cách tương tự. Chúng sẽ được liên kết với một lệnh được thực thi khi người dùng tương tác với phần tử GUI. Các phần tử liên quan đến các hoạt động giống nhau sẽ được liên kết với các lệnh giống nhau, ngăn chặn mọi sự trùng lặp mã.

Do đó, các lệnh trở thành một lớp trung gian thuận tiện giúp giảm sự ghép nối giữa GUI và các lớp logic nghiệp vụ. Và đó chỉ là một phần nhỏ trong số những lợi ích mà Mẫu Command có thể mang lại!

Cấu trúc

  1. Lớp Sender (người gửi, hay còn gọi là invoker) chịu trách nhiệm khởi tạo các yêu cầu. Lớp này phải có một trường để lưu trữ một tham chiếu đến một đối tượng lệnh. Người gửi kích hoạt lệnh đó 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 lệnh được tạo trước từ client thông qua phương thức khởi tạo.
  2. Giao diện Command 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 phép tự thực hiện công việc, mà là để chuyển lời gọi lệnh đến một trong các đối tượng logic nghiệp vụ. Tuy nhiên, để đơn giản hóa mã, các lớp này có thể được hợp nhất.
    Các tham số cần thiết để thực thi một phương thức trên đối tượng nhận có thể được khai báo dưới dạng các trường trong concrete command. 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 hàm tạo.
  4. Lớp Receiver (người nhận) chứa một số logic nghiệp vụ. Hầu hết mọi đối tượng đều có thể hoạt động như một receiver. 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 đối tượng Concrete command. Client phải chuyển tất cả các tham số yêu cầu, bao gồm cả thể hiện của receiver, 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.

Khả năng áp dụng

Sử dụng mẫu lệnh khi bạn muốn tham số hóa các đối tượng bằng các thao tác.

Sử dụng mẫu 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 mẫu lệnh khi bạn muốn thực hiện các hoạt động có thể đảo ngược.

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

😄😄😄

Nguyên tắc Trách nhiệm Đơn lẻ. 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 này.

Nguyên tắc Mở / Đóng. Bạn có thể đưa các lệnh mới vào ứng dụng mà không cần phá vỡ mã ứng dụng hiện có.

Bạn có thể thực hiện hoàn tác undo /redo.

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 lệnh phức tạp

🙁🙁🙁

Mã có thể trở nên phức tạp hơn vì bạn đang giới thiệu một lớp hoàn toàn mới giữa sender và receiver.

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 và người nhận yêu cầu:
    • 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ố chúng xử lý yêu cầu.
    • 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.
  • 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.
  • Bạn có thể sử dụng Command và Memento cùng nhau khi thực hiện “undo”. Trong trường hợp này, các lệnh chịu trách nhiệm thực hiện các hoạt động khác nhau trên một đối tượng đích, trong khi các mementos lưu trạng thái của đối tượng đó ngay trước khi lệnh được thực thi.
  • Command và Strategy có thể trông giống nhau vì bạn có thể sử dụng cả hai để tham số hóa một đối tượng bằng một số hành động. Tuy nhiên, chúng có mục tiêu rất khác nhau.
    • Bạn có thể sử dụng Command để chuyển đổi bất kỳ thao tác nào thành một đối tượng. Các tham số của hoạt động trở thành các trường của đối tượng đó. Việc chuyển đổi cho phép bạn trì hoãn việc thực hiện thao tác, xếp hàng đợi, lưu trữ lịch sử các lệnh, gửi lệnh đến các dịch vụ từ xa, v.v.
    • Trong khi, Strategy thường mô tả các cách khác nhau để thực hiện cùng một việc, cho phép bạn hoán đổi các thuật toán này trong một lớp ngữ cảnh duy nhất.
  • Prototype có thể hữu ích khi bạn cần lưu các bản sao của Command vào lịch sử.
  • Bạn có thể coi Visitor như một phiên bản mạnh mẽ của mẫu Command. Các đối tượng của nó có thể thực thi các hoạt động trên các đối tượng khác nhau của các lớp khác nhau.

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 *