Khái niệm

Command là một Pattern dạng hành vi, cho phép biến một yêu cầu thành một đối tượng độc lập và chứa đầy đủ thông tin về yêu cầu đó. Nói cho dể hiểu, nó cho phép tất cả những Request gửi đến object được lưu trong chính object đó dưới dạng một object Command.

Đặt vấn đề

Giả sử bạn đang làm trên một ứng dụng soạn thảo văn bản mới. Công việc của bạn hiện tại là tạo một thanh toolbar với vài nút button để chỉnh sữa. Giờ bạn tạo một lớp cơ sở Button để sử dụng cho các nút button trên toolbar.

Các nút button có vẻ rất giống nhau, nhưng sự kiện của từng nút thì lại khác nhau. Làm sao để gắn sự kiện cho từng nút button ? Cách đơn giản nhất là tạo các lớp riêng cho từng sự kiện và kế thừa lại lớp cơ sở Button

Sau một thời gian, 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ó rất nhiều lớp con, và điều này vẫn ổn nếu như không phá mã trong các lớp khi lớp cơ sỡ Button thay đổi.

Vấn đề tới nhất sẽ đến, khi ứng dụng của bạn phát triên cao hơn, người dùng không chỉ muốn nhấn vào các nút để thực hiện chức năng, mà còn thực hiện thông qua phím tắt hoặc vài thao tác kéo thả. Như vậy bạn phải một lớp khác bắt sự kiến phím tắt, sau đó lại copy toàn bộ chức năng ở nút button đã implement. Điều khá tốt những khi có việc cần thay đổi chức năng bạn phải cập nhật ở nhiều nơi.

Giải pháp

Để giải quyết vấn đề trên, ta sẽ tách phần giao diện và phần logic ra hai lớp khác nhau. Lớp GUI (Giao Diện) sẽ đảm nhiệm vài trò thể hiện các giao diện đẹp ra cho người dùng sử dung, đồng thời sẽ lắng nghe và bắt các sự khi người dùng sử dụng. Tuy nhiên, khi cần đến những việc tính toán, lưu trữ dữ liệu, hoặc truy vấn dữ liệu thì phần GUI nên giao lại cho phần logic đảm nhiệm.

Nói đến đây các bạn sẽ nghỉ chỉ cần tách ra một lớp logic bắt và xử lý các logic các sử kiện nút Button thì đã xong.

Nhưng với Command Pattern đề nghị bạn đừng nên dùng GUI gọi yêu cầu trực tiếp. Bạn nên trích các thông tin, yêu cầu , tên phương được gọi……vào một lớp đặc biệt cùng với một phương thức để kích hoạt yêu cầu.

Như vậy ta sẽ giảm được sử ghép nối giữa GUI và Logic, giúp bạn có thể Linh hoạt hơn trong sử dụng

Cấu Trúc

  • Command: là interface hoặc abstract class, chứa một phương thức duy nhất để executing lệnh.
  • Concrete Command: Thực hiện các yêu cầu khác nhau. Một Concrete Command không phải tự mình thực hiện công việc, thường sẽ chuyển đến lớp logic chuyên nghiệp.
  • Client: Tiếp nhận request từ phía người dùng, đống gói request thành ConcreteCommand thích hợp.
  • Invoker: Tiếp nhận Concrete Command từ Client và gọi execute()
  • Receiver: Đây là thành phần thực thi logic chuyên nghiệp.

Ví dụ áp dụng Command Pattern

Account.kt

class Account {
    var name : String

    constructor(name: String) {
        this.name = name
    }

    fun open() {
        println("Account $name Opened")
    }

    fun close() {
        println("Account $name Closed")
    }

}

Command.kt

interface Command {
    fun execute()
}

OpenAccount.kt

class OpenAccount : Command{
    var account: Account

    constructor(account: Account) {
        this.account = account
    }

    override fun execute() {
        account.open()
    }
}

CloseAccount.kt

class CloseAccount : Command {
    var account: Account

    constructor(account: Account) {
        this.account = account
    }


    override fun execute() {
        account.close()
    }
}

BankApp.kt

class BankApp {
    private var openAccount: Command? = null
    private var closeAccount: Command? = null

    constructor(openAccount: Command?, closeAccount: Command?) {
        this.openAccount = openAccount
        this.closeAccount = closeAccount
    }


    fun clickOpenAccount() {
        println("User click open an account")
        openAccount!!.execute()
    }

    fun clickCloseAccount() {
        println("User click close an account")
        closeAccount!!.execute()
    }
}

main.kt

fun main(args: Array<String>) {
    var account : Account = Account("NickSeven")

    var openAccount : Command = OpenAccount(account)

    var closeAccount : Command = CloseAccount(account)

    var bankApp : BankApp = BankApp(openAccount,closeAccount)

    bankApp.clickOpenAccount()

    bankApp.clickCloseAccount()
}

Kết quả

User click open an account
Account NickSeven Opened
User click close an account
Account NickSeven Closed

Process finished with exit code 0

Khi nào áp dụng

Khi cần tham số hóa các đối tượng theo một hành động thực hiện.

Khi cần tạo và thực thi các yêu cầu vào các thời điểm khác nhau.