Khi tất cả đều là mệnh lệnh!

Hãy tưởng tượng chúng ta có một đoạn code thế này:

public class EventHandlers {
    public void handle(Event event) {
        EventType evenType = event.getType();
        if(eventType == EvenType.USER_LOGIN) {
            // xử lý sự kiện user đăng nhập
        }
        else if(eventType == EvenType.USER_UPDATE) {
            // xử lý sự kiện user cập nhật thông tin
        }
    }
}

Câu hỏi đặt ra là, chúng ta sẽ làm thế nào khi có thêm sự kiện USER_LOGOUT? Đương nhiên rồi, chúng ta sẽ thêm một nhánh else if nữa như thế này:

else if(eventType == EvenType.USER_LOGOUT) {
    // xử lý sự kiện user thoát ứng dụng
}

Nhưng có điều gì đó không ổn, rõ ràng là chúng ta đang vi phạm nguyên tắc #OpenClose (đóng với việc thay đổi, mở với việc mở rộng), chúng ta sẽ không chỉ có thêm sự kiện USER_LOGOUT và còn nhiều sự kiện khác thì sao, lớp EventHandlers sẽ bị dài mãi ra, chúng ta sẽ phải test lại toàn bộ các chức năng liên quan đến lớp này và đến một lúc nào đó việc phải thêm code vào lớp EventHandlers sẽ giống như một trò chơi mạo hiểm mà chúng ta không bao giờ muốn chơi.

Mục tiêu ra đời

Vậy phải làm thế nào để lớp EventHandlers trở nên mềm dẻo và dễ mở rộng, câu trả lời sẽ đến từ #Command pattern. Command pattern ra đời với mục tiêu:

  • Loại bỏ được việc sử dụng if else từ đó xoá bỏ được mối liên kết chặt chẽ giữa lớp gọi và hàm xử lý, ở đây lớp gọi là EventHandlers và hàm xử lý là handle
  • Cho phép chúng ta dễ dàng mở rộng (thêm mới các sự kiên hay yêu cầu và lớp xử nó) và tối thiểu hoá việc ảnh hưởng đến lớp gọi (EventHandlers)
  • Cho phép chúng ta dễ dàng thay đổi lớp xử lý cho các sự kiện hay các yêu cầu

Áp dụng vào thực tiễn

Áp dụng command pattern cho lớp EventHandlers chúng ta sẽ có:

public class EventHandlers {
    private final Map<EventType, EventHandler> eventHandlers;
    public EventHandlers() {
        // trong dự án thật, chúng ta có thể cân nhắc thêm hàm addHandler thay vi khởi tạo trong hàm tạo
        this.eventHandlers = new HashMap<>();
        this.eventHandlers.put(EventType.USER_LOGIN, new UserLoginHandler());
        this.eventHandlers.put(EventType.USER_UPDATE, new UserUpdateHandler());
        this.eventHandlers.put(EventType.USER_LOGOUT, new UserLogoutHandler());
    }
    public void handle(Event event) {
        EventHandler handler = eventHandlers.get(event.getEventType());
        handler.handle(event);
    }
}

Bạn có thể thấy được rằng hàm handle đã trở nên rất ngắn gọn, và sau này cho dù có bao nhiêu event khác đi chăng nữa thì nó cũng sẽ không bị thay đổi và chúng ta sẽ bảo toàn được nguyên tắc OpenClose

Tổng kết

Command #designpattern cũng na ná giống với #Observer pattern, tuy nhiên sự khác biệt nằm ở chỗ:

  • Observer là bị động chờ được gọi, còn Command sẽ chủ động gọi đến các lớp xử lý
  • Observer hay được sử dụng trong xử lý #async còn Command pattern hay dùng để xử lý #sync
  • Observer sẽ hạn chế việc xử lý các nghiệm vụ nặng nề, còn Command thì lại chuyên dụng cho việc này

Tham khảo

  1. Wiki
  2. Source code