Mẫu Lệnh trong Java: Tách Rời Yêu Cầu và Xử Lý

【Độ khó: ★★★☆☆, Tần suất sử dụng: ★★★★☆】

Khi thiết kế hệ thống điều khiển từ xa, thiết bị điều khiển (ví dụ: remote TV) không cần biết chi tiết về thiết bị đích (TV). Thay vào đó, mỗi thao tác như bật/tắt, thay đổi kênh được封装 thành đối tượng lệnh. Điều khiển chỉ gửi lệnh đến đối tượng trung gian, và chính đối tượng này mới thực hiện hành động cụ thể trên thiết bị đích. Cách tiếp cận này loại bỏ sự phụ thuộc trực tiếp giữa người gửi và người nhận.

Trong phát triển phần mềm, mẫu lệnh (Command Pattern) giúp giải quyết bài toán tương tự. Ví dụ, một nút bấm trong giao diện người dùng có thể kích hoạt nhiều hành động khác nhau (mở file, lưu dữ liệu, đóng cửa sổ) mà không cần biết chi tiết xử lý. Việc áp dụng mẫu lệnh giúp tăng tính linh hoạt và bảo trì hệ thống.

Thiết kế Nút Chức Năng Linh Hoạt

Công ty Sunny phát triển ứng dụng OA desktop với nút chức năng tùy chỉnh. Mỗi người dùng có thể gán nút thành các tác vụ khác nhau (mở tài liệu, thu nhỏ cửa sổ...). Giao diện cấu hình được thiết kế như Hình 1:

Giao diện cấu hình nút chức năng

Cách triển khai truyền thống gặp vấn đề:

// NútChứcNăng: Người gửi yêu cầu
class NútChứcNăng {
    private XửLýTrợGiúp trợGiúp;
    public void nhấnNút() {
        trợGiúp = new XửLýTrợGiúp();
        trợGiúp.hiểnThị();
    }
}

Vấn đề chính:

  • Độ耦合 cao: Thay đổi xử lý cần sửa mã nguồn nút
  • Thêm mới tác vụ đòi hỏi tạo lớp mới, làm tăng số lượng lớp
  • Không linh hoạt khi thay đổi hành động tại runtime

Cấu trúc Mẫu Lệnh

Mẫu lệnh giới thiệu lớp Lệnh trung gian để tách rời:

  • Lệnh (Abstract Command): Khai báo phương thức thựcThi()
  • LệnhCụThể (Concrete Command): Triển khai thựcThi() bằng cách gọi phương thức trên đối tượng xử lý
  • NgườiGọi (Invoker): Giữ tham chiếu đến lệnh, kích hoạt khi cần
  • XửLý (Receiver): Thực hiện hành động cụ thể

Ví dụ triển khai:

abstract class Lệnh {
    public abstract void thựcThi();
}

class NgườiGọi {
    private Lệnh yêuCầu;
    public void gánYêuCầu(Lệnh yêuCầu) {
        this.yêuCầu = yêuCầu;
    }
    public void kíchHoạt() {
        yêuCầu.thựcThi();
    }
}

class LệnhTrợGiúp extends Lệnh {
    private XửLýTrợGiúp xửLý;
    public LệnhTrợGiúp(XửLýTrợGiúp xửLý) {
        this.xửLý = xửLý;
    }
    public void thựcThi() {
        xửLý.đọcTàiLiệu();
    }
}

class XửLýTrợGiúp {
    public void đọcTàiLiệu() {
        System.out.println("Đang mở tài liệu trợ giúp");
    }
}

NgườiGọi không cần biết chi tiết xử lý, chỉ cần gọi kíchHoạt(). Thay đổi hành động chỉ cần thay thế đối tượng LệnhCụThể mà không cần sửa mã nguồn NgườiGọi.

Triển khai Thực Tế

Đoạn mã hoàn chỉnh cho ứng dụng OA:

class CửaSốCàiĐặt {
    private List<NútChứcNăng> nútList = new ArrayList<>();
    public void thêmNút(NútChứcNăng nút) {
        nútList.add(nút);
    }
    public void hiểnThị() {
        System.out.println("Giao diện cấu hình");
        for (NútChứcNăng nút : nútList) {
            System.out.println("- " + nút.tên);
        }
    }
}

class NútChứcNăng {
    private String tên;
    private Lệnh yêuCầu;
    public NútChứcNăng(String tên) { this.tên = tên; }
    public void gánYêuCầu(Lệnh yêuCầu) { this.yêuCầu = yêuCầu; }
    public void nhấn() {
        System.out.print("Nút " + tên + ": ");
        yêuCầu.thựcThi();
    }
}

// Cấu hình từ XML
class XMLConfig {
    public static Lệnh lấyLệnh(int loại) {
        // Đọc config.xml và tạo đối tượng
        return (Lệnh) Class.forName("HelpCommand").newInstance();
    }
}

class Client {
    public static void main(String[] args) {
        CửaSốCàiĐặt cửaSố = new CửaSốCàiĐặt();
        NútChứcNăng nút1 = new NútChứcNăng("Nút 1");
        NútChứcNăng nút2 = new NútChứcNăng("Nút 2");
        
        nút1.gánYêuCầu(new LệnhTrợGiúp(new XửLýTrợGiúp()));
        nút2.gánYêuCầu(new LệnhThuNhỏ(new XửLýCửaSổ()));
        
        cửaSố.thêmNút(nút1);
        cửaSố.thêmNút(nút2);
        cửaSố. hiểnThị();
        nút1.nhấn(); // "Nút 1: Đang mở tài liệu trợ giúp"
        nút2.nhấn(); // "Nút 2: Đang thu nhỏ cửa sổ"
    }
}

Để thêm tính năng mới (ví dụ: chụp màn hình), chỉ cần tạo lớp LệnhChụpMànHình và cấu hình trong XML mà không cần sửa mã nguồn hiện có.

Ứng Dụng Mở Rộng

Hoàn tác (Undo): Thêm phương thức hoànTác() trong lớp Lệnh để thực hiện hành động ngược lại.

abstract class Lệnh {
    public abstract void thựcThi();
    public abstract void hoànTác();
}

class LệnhCộng extends Lệnh {
    private SốHọc máyTính;
    private int giáTrị;
    public void thựcThi() {
        giáTrị = máyTính.cộng(10);
    }
    public void hoànTác() {
        máyTính.cộng(-giáTrị);
    }
}

Chuỗi Lệnh (Macro Command): Kết hợp nhiều lệnh thành một lệnh tổng hợp để xử lý hàng loạt tác vụ.

Thẻ: command-pattern Java design-patterns oop software-architecture

Đăng vào ngày 19 tháng 5 lúc 15:27