Design Patterns: Command

Command còn có tên gọi khác là Action, Transaction.

Mục đích

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. Việc 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 hiện yêu cầu và hỗ trợ các hoạt động hoàn tác.

Design Patterns: Command

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.
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 chúng đều phải làm những việc khác nhau. Bạn sẽ đặt mã xử lý sự kiện nhấp chuột của các nút này ở đâu? 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 kế thừa từ lớp Button
Rất nhiều lớp con kế thừa từ lớp Button

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 vụng về vào mã dễ thay đổi của logic nghiệp vụ.

Một số lớp thực hiện cùng một chức năng.
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ư Copy / Paste 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 của chúng tôi chỉ có thanh công cụ, bạn 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 nút. 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, phím tắt và những thứ khác, bạn phải sao chép mã của hoạt động 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 thực hiện một việc quan trọng, 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 đoạn 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ó một số đố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ụ.
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ụ.

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 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à nó sẽ được xử lý như thế nào. Đố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.
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 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 lệnh 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 lớp lệnh cụ thể. Như một phần thưởng, bây giờ 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 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 lệnh 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ó thể tự lấy nó.

Các đối tượng GUI ủy quyền công việc cho các command.
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 của chúng ta. Sau khi áp dụng Command pattern, chúng ta không cần tất cả các lớp button con đó để xử lý các sự kiện nhấp chuột. 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 command và làm cho nút thực hiện lệnh đó khi nhấp chuột.

Bạn sẽ triển khai một loạt các lớp command 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ự định của các nút.

Các phần tử GUI khác, chẳng hạn như menu, phím 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. Như bạn có thể đoán bây giờ, 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 command giống nhau, ngăn chặn bất kỳ sự trùng lặp mã nào.

Kết quả là, command trở thành một lớp trung gian thuận tiện làm giảm sự phụ thuộc giữa GUI và các lớp logic nghiệp vụ. Và đó chỉ là một phần nhỏ trong những lợi ích mà command pattern có thể mang lại!

Command pattern trong thế giới thực

Thực hiện một order trong một nhà hàng.
Thực hiện một order trong một nhà hàng.

Sau một chuyến đi bộ dài trong thành phố, bạn đến một nhà hàng đẹp và ngồi vào bàn bên cửa sổ. Một người phục vụ thân thiện tiếp bạn và nhanh chóng nhận đặt món của bạn, viết nó ra một tờ giấy. Người phục vụ đi vào bếp và dán tờ giấy order lên tường. Sau một thời gian, các món bạn order được chuyển đến đầu bếp, người sẽ đọc và nấu bữa ăn cho bạn. Người đầu bếp đặt bữa ăn vào khay cùng với order của bạn. Người phục vụ nhận khay thức ăn, kiểm tra order để đảm bảo mọi thứ đều như bạn mong muốn và mang mọi thứ đến bàn của bạn.

Tờ giấy order dùng như một mệnh lệnh. Nó vẫn còn trong hàng đợi cho đến khi đầu bếp sẵn sàng phục vụ nó. Order có tất cả các thông tin liên quan cần thiết để nấu bữa ăn. Nó cho phép đầu bếp bắt đầu nấu ăn ngay lập tức thay vì chạy xung quanh để làm rõ chi tiết order trực tiếp từ bạn.

Cấu trúc của Command pattern

  1. Lớp Sender (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 command. 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 command. 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 khai báo chỉ một phương thức duy nhất để thực hiện command.
  3. Các lớp Command1Command2 (Concrete Commands) thực hiện nhiều loại command khác nhau. Một command cụ thể không phải tự thực hiện công việc, mà là để chuyển lệnh gọi đế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 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. Lớp Receiver 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 người nhận. Hầu hết các command 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. Lớp Client tạo và cấu hình các đối tượng command cụ thể. Client phải truyền tất cả các tham số của command, bao gồm cả đối tượng người nhận, vào phương thức khởi tạo của command. Sau đó, command có thể được liên kết với một hoặc nhiều người gửi.

Mã giả

Trong ví dụ này, Command pattern giúp theo dõi lịch sử của các hoạt động đã thực thi và giúp bạn có thể hoàn tác một hoạt động nếu cần.

Ví dụ về command pattern

Các lệnh dẫn đến thay đổi trạng thái của trình soạn thảo (ví dụ: cut và paste) tạo một bản sao lưu trạng thái của trình soạn thảo trước khi thực hiện một thao tác được liên kết với lệnh. Sau khi một lệnh được thực thi, nó được đặt vào lịch sử lệnh (một ngăn xếp các đối tượng command) cùng với bản sao lưu trạng thái của trình soạn thảo tại thời điểm đó. Sau đó, nếu người dùng cần hoàn tác một thao tác, ứng dụng có thể lấy lệnh gần đây nhất từ ​​lịch sử, đọc bản sao lưu liên quan về trạng thái của trình chỉnh sửa và khôi phục nó.

Mã client (các phần tử GUI, lịch sử lệnh, v.v.) không được kết hợp với các lớp command cụ thể vì nó hoạt động với các command thông qua interface Command. Cách tiếp cận này cho phép bạn thêm các lệnh mới vào ứng dụng mà không phá vỡ bất kỳ mã hiện có nào.

// The base command class defines the common interface for all
// concrete commands.
abstract class Command is
    protected field app: Application
    protected field editor: Editor
    protected field backup: text

    constructor Command(app: Application, editor: Editor) is
        this.app = app
        this.editor = editor

    // Make a backup of the editor's state.
    method saveBackup() is
        backup = editor.text

    // Restore the editor's state.
    method undo() is
        editor.text = backup

    // The execution method is declared abstract to force all
    // concrete commands to provide their own implementations.
    // The method must return true or false depending on whether
    // the command changes the editor's state.
    abstract method execute()


// The concrete commands go here.
class CopyCommand extends Command is
    // The copy command isn't saved to the history since it
    // doesn't change the editor's state.
    method execute() is
        app.clipboard = editor.getSelection()
        return false

class CutCommand extends Command is
    // The cut command does change the editor's state, therefore
    // it must be saved to the history. And it'll be saved as
    // long as the method returns true.
    method execute() is
        saveBackup()
        app.clipboard = editor.getSelection()
        editor.deleteSelection()
        return true

class PasteCommand extends Command is
    method execute() is
        saveBackup()
        editor.replaceSelection(app.clipboard)
        return true

// The undo operation is also a command.
class UndoCommand extends Command is
    method execute() is
        app.undo()
        return false


// The global command history is just a stack.
class CommandHistory is
    private field history: array of Command

    // Last in...
    method push(c: Command) is
        // Push the command to the end of the history array.

    // ...first out
    method pop():Command is
        // Get the most recent command from the history.


// The editor class has actual text editing operations. It plays
// the role of a receiver: all commands end up delegating
// execution to the editor's methods.
class Editor is
    field text: string

    method getSelection() is
        // Return selected text.

    method deleteSelection() is
        // Delete selected text.

    method replaceSelection(text) is
        // Insert the clipboard's contents at the current
        // position.


// The application class sets up object relations. It acts as a
// sender: when something needs to be done, it creates a command
// object and executes it.
class Application is
    field clipboard: string
    field editors: array of Editors
    field activeEditor: Editor
    field history: CommandHistory

    // The code which assigns commands to UI objects may look
    // like this.
    method createUI() is
        // ...
        copy = function() { executeCommand(
            new CopyCommand(this, activeEditor)) }
        copyButton.setCommand(copy)
        shortcuts.onKeyPress("Ctrl+C", copy)

        cut = function() { executeCommand(
            new CutCommand(this, activeEditor)) }
        cutButton.setCommand(cut)
        shortcuts.onKeyPress("Ctrl+X", cut)

        paste = function() { executeCommand(
            new PasteCommand(this, activeEditor)) }
        pasteButton.setCommand(paste)
        shortcuts.onKeyPress("Ctrl+V", paste)

        undo = function() { executeCommand(
            new UndoCommand(this, activeEditor)) }
        undoButton.setCommand(undo)
        shortcuts.onKeyPress("Ctrl+Z", undo)

    // Execute a command and check whether it has to be added to
    // the history.
    method executeCommand(command) is
        if (command.execute)
            history.push(command)

    // Take the most recent command from the history and run its
    // undo method. Note that we don't know the class of that
    // command. But we don't have to, since the command knows
    // how to undo its own action.
    method undo() is
        command = history.pop()
        if (command != null)
            command.undo()

Khả năng áp dụng

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

Command pattern có thể biến một lời gọi phương thức cụ thể thành một đối tượng độc lập. Thay đổi này mở ra rất nhiều cách sử dụng thú vị: bạn có thể chuyển các lệnh làm đối số của phương thức, lưu trữ chúng bên trong các đối tượng khác, chuyển đổi các lệnh được liên kết trong thời gian chạy, v.v.

Đây là một ví dụ: bạn đang phát triển thành phần GUI chẳng hạn như menu ngữ cảnh và bạn muốn người dùng của mình có thể cấu hình các mục menu kích hoạt hoạt động khi người dùng cuối nhấp vào một phần tử.

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

Như với bất kỳ đối tượng nào khác, một command có thể được serialize, có nghĩa là chuyển đổi nó thành một chuỗi để có thể dễ dàng ghi vào tệp hoặc cơ sở dữ liệu. Sau đó, chuỗi có thể được deserialize thanhf đối tượng command ban đầu. Do đó, bạn có thể trì hoãn và lên lịch thực hiện command. Nhưng còn nhiều hơn thế nữa! Theo cách tương tự, bạn có thể tạo hàng đợi, ghi nhật ký hoặc gửi command qua mạng.

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

Mặc dù có nhiều cách để thực hiện hoàn tác / làm lại, nhưng Command pattern có lẽ là phổ biến nhất.

Để có thể hoàn tác các hoạt động, bạn cần thực hiện lịch sử của các hoạt động đã thực hiện. Lịch sử lệnh là một ngăn xếp chứa tất cả các đối tượng command được thực thi cùng với các bản sao lưu liên quan về trạng thái của ứng dụng.

Phương pháp này có hai nhược điểm. Đầu tiên, không dễ để lưu trạng thái của ứng dụng vì một số trong số đó có thể là riêng tư. Vấn đề này có thể được giảm thiểu với mẫu Memento.

Thứ hai, các bản sao lưu trạng thái có thể tiêu tốn khá nhiều RAM. Do đó, đôi khi bạn có thể sử dụng một cách triển khai thay thế: thay vì khôi phục trạng thái trước đây, lệnh thực hiện thao tác ngược. Hoạt động ngược lại cũng có một cái giá: nó có thể khó hoặc thậm chí không thể thực hiện được.

Cách triển khai

  1. Khai báo Command interface với một phương thức Execute duy nhất.
  2. Bắt đầu trích xuất các yêu cầu vào các lớp Command cụ thể triển khai từ Command interface. Mỗi lớp 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 Command.
  3. Định nghĩa các lớp sẽ hoạt động như người gửi. Thêm các trường để lưu trữ Command vào các lớp này. Người gửi chỉ nên giao tiếp với các Command của chúng thông qua Command interface. Người gửi thường không tự tạo các đối tượng Command mà lấy chúng từ lớp Client.
  4. Thay đổi lớp người gửi để chúng thực hiện command 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:
  • Tạo người nhận.
  • Tạo Command và liên kết chúng với người nhận nếu cần.
  • Tạo người gửi và liên kết chúng với các command cụ thể.

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

Ưu điểm Nhược điểm
  • Nguyên tắc Single Responsibility. 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 Open/Closed. 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 / làm lại.
  • Bạn có thể triển khai thực hiện hoãn lại các lệnh.
  • 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.
  • Code 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 người gửi và người nhận.

Mối quan hệ với các design pattern khác

Chain of Responsibility, Command, MediatorObserver 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ý 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 họ 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ý và hủy đăng ký nhận yêu cầu độ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 thể hiệ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 CommandMemento cùng nhau khi thực hiện "hoàn tác". 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.

CommandStrategy 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 đích 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 hoạt động, 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.

Ví dụ về Command pattern

Command pattern trong C#

Độ phức tạp: 2/5

Phổ biến: 5/5

Ví dụ sử dụng: Command pattern khá phổ biến trong mã C#. Thông thường, nó được sử dụng như một sự thay thế cho các lệnh gọi lại để tham số hóa các phần tử giao diện người dùng bằng các hành động. Nó cũng được sử dụng cho các tác vụ xếp hàng, theo dõi lịch sử hoạt động, v.v.

Nhận biết: Command pattern có thể nhận biết được bằng các phương thức trong abstract class/interface (người gửi). Phương thức này gọi một phương thức được triển khai trong một abstract class/interface khác (người nhận) đã được đóng gói bởi việc triển khai command trong quá trình tạo nó. Các lớp command thường được giới hạn trong các hành động cụ thể.

Ví dụ sau minh họa cấu trúc của Command pattern. Nó tập trung vào việc trả lời những câu hỏi sau:

  • Command pattern bao gồm những lớp nào?
  • Các lớp này có vai trò gì?
  • Các thành phần của Command pattern có liên quan với nhau theo cách nào?
using System;

namespace Comdy.DesignPattern.Command.Conceptual
{
    // The Command interface declares a method for executing a command.
    public interface ICommand
    {
        void Execute();
    }

    // Some commands can implement simple operations on their own.
    class SimpleCommand : ICommand
    {
        private string _payload = string.Empty;

        public SimpleCommand(string payload)
        {
            this._payload = payload;
        }

        public void Execute()
        {
            Console.WriteLine($"SimpleCommand: See, I can do simple things like printing ({this._payload})");
        }
    }

    // However, some commands can delegate more complex operations to other
    // objects, called "receivers."
    class ComplexCommand : ICommand
    {
        private Receiver _receiver;

        // Context data, required for launching the receiver's methods.
        private string _a;

        private string _b;

        // Complex commands can accept one or several receiver objects along
        // with any context data via the constructor.
        public ComplexCommand(Receiver receiver, string a, string b)
        {
            this._receiver = receiver;
            this._a = a;
            this._b = b;
        }

        // Commands can delegate to any methods of a receiver.
        public void Execute()
        {
            Console.WriteLine("ComplexCommand: Complex stuff should be done by a receiver object.");
            this._receiver.DoSomething(this._a);
            this._receiver.DoSomethingElse(this._b);
        }
    }

    // The Receiver classes contain some important business logic. They know how
    // to perform all kinds of operations, associated with carrying out a
    // request. In fact, any class may serve as a Receiver.
    class Receiver
    {
        public void DoSomething(string a)
        {
            Console.WriteLine($"Receiver: Working on ({a}.)");
        }

        public void DoSomethingElse(string b)
        {
            Console.WriteLine($"Receiver: Also working on ({b}.)");
        }
    }

    // The Invoker is associated with one or several commands. It sends a
    // request to the command.
    class Invoker
    {
        private ICommand _onStart;

        private ICommand _onFinish;

        // Initialize commands.
        public void SetOnStart(ICommand command)
        {
            this._onStart = command;
        }

        public void SetOnFinish(ICommand command)
        {
            this._onFinish = command;
        }

        // The Invoker does not depend on concrete command or receiver classes.
        // The Invoker passes a request to a receiver indirectly, by executing a
        // command.
        public void DoSomethingImportant()
        {
            Console.WriteLine("Invoker: Does anybody want something done before I begin?");
            if (this._onStart is ICommand)
            {
                this._onStart.Execute();
            }
            
            Console.WriteLine("Invoker: ...doing something really important...");
            
            Console.WriteLine("Invoker: Does anybody want something done after I finish?");
            if (this._onFinish is ICommand)
            {
                this._onFinish.Execute();
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // The client code can parameterize an invoker with any commands.
            Invoker invoker = new Invoker();
            invoker.SetOnStart(new SimpleCommand("Say Hi!"));
            Receiver receiver = new Receiver();
            invoker.SetOnFinish(new ComplexCommand(receiver, "Send email", "Save report"));

            invoker.DoSomethingImportant();
        }
    }
}

Kết quả:

Invoker: Does anybody want something done before I begin?
SimpleCommand: See, I can do simple things like printing (Say Hi!)
Invoker: ...doing something really important...
Invoker: Does anybody want something done after I finish?
ComplexCommand: Complex stuff should be done by a receiver object.
Receiver: Working on (Send email.)
Receiver: Also working on (Save report.)
Design PatternLập Trình C#
Bài Viết Liên Quan:
Design Patterns: Visitor
Trung Nguyen 15/01/2021
Design Patterns: Visitor

Visitor là một mẫu thiết kế hành vi cho phép bạn tách các thuật toán khỏi các đối tượng mà chúng hoạt động.

Design Patterns: Template Method
Trung Nguyen 15/01/2021
Design Patterns: Template Method

Template Method định nghĩa khung của một thuật toán trong lớp cha cho phép lớp con ghi đè các bước cụ thể của thuật toán mà không thay đổi cấu trúc của nó.

Design Patterns: Strategy
Trung Nguyen 15/01/2021
Design Patterns: Strategy

Strategy cho phép bạn định nghĩa một nhóm thuật toán, đặt mỗi thuật toán vào một lớp riêng biệt và làm cho các đối tượng của chúng có thể hoán đổi cho nhau.

Design Patterns: State
Trung Nguyen 15/01/2021
Design Patterns: State

State là một mẫu thiết kế hành vi cho phép một đối tượng thay đổi hành vi của nó khi trạng thái bên trong của nó thay đổi.