Một thanh niên trước khi đi thi chương trình Ai là triệu phú, đã cẩn thận dặn bố ở nhà ngồi “chờ” điện thoại để nếu con cần sự trợ giúp từ người thân thì sẽ gọi cho bố.

Một thanh niên khác ngồi ngoài cửa phòng “chờ” nhân sự gọi vào phỏng vấn xin việc.

Một hàm xử lý tính năng gửi tin nhắn được viết sẵn trong Controller “chờ” người dùng bấm nút Gửi trên giao diện là thực thi hàm.

Ở đây tôi sử dụng từ “chờ” thực ra vẫn chưa chính xác, nhưng có vẻ nó dễ hiểu hơn. Thực tế Listener nó đang bất động, không đếm thời gian hay gì cả, chỉ khi thằng Event vỗ vai nó mới tỉnh dậy và làm công việc của nó.

VD trong 1 số thư viện hoặc function có sẵn, chắc các bạn cũng từng nhìn qua các cách viết này

Javascript:

var jqxhr = $.post( "example.php", function() {
  alert( "Post success" );
})
.done(function() {
  alert( "Post done`" );
})
.fail(function() {
  alert( "Post error" );
})
.always(function() {
  alert( "Post finished" );
});

Các lệnh alert là nội dung của function listener, nhưng nó lại không được thực thi luôn sau khi chạy chương trình, mà chỉ thực thi khi gặp trạng thái phản hồi từ $.post().

Java swing:

package net.tunghuynh.listener;
public class DemoInterface extends Frame implements ActionListener {
  Button b;
  //...
  public DemoInterface() {
    b = new Button("Click me");
    b.addActionListener(this);
  }
  @Override
  public void actionPerformed(ActionEvent e) {
    System.out.print("Clicked");
  }
}

Hoặc viết gọn hơn

package net.tunghuynh.listener;
public class DemoInterface extends Frame{
  Button b;
  //...
  public DemoInterface() {
    b = new Button("Click me");
    b.addActionListener(new ActionListener() { 
      public void actionPerformed(ActionEvent e) { 
      System.out.print("Clicked");
      } 
    });
  }
}

Bạn có bao giờ thắc mắc thao tác đó được xây dựng lên như thế nào, tại sao nó lại hoạt động được??? Không phức tạp lắm, chỉ viết code là chạy thôi. Bài viết này tôi sẽ không hướng dẫn các bạn cách sử dụng các listener có sẵn, mà tôi sẽ hướng dẫn các bạn cách tạo ra 1 cái của riêng bạn. Hiểu được cách làm này nghĩa là bạn đã tự nhiên nắm được 2/5 nguyên lý của SOLID đó là Open/Closed và Dependency Inversion

Bạn sẽ ứng dụng được gì với cái này!

  • Tạo được các component tái sử dụng chung, giảm thiểu trùng lặp code
  • Với các hệ thống lớn gồm nhiều hệ thống con thì việc các hệ thống con giao tiếp với nhau qua interface dưới dạng webservice là cách hợp lý nhất. Trong đó hệ thống con ở đây củng có thể là 1 Web app, Mobile app, Desktop app.

Giả sử bạn đã tạo được một control trên giao diện là SearchBox của chức năng Danh sách nhân viên. Cái này vừa có thể nhập ký tự để tìm kiếm, vừa có thể gợi ý lịch sử từ khóa, lại có thêm tính năng tìm kiếm bằng giọng nói. Nhưng code xử lý của nó thì quá dài, xong lại phải căn chỉnh giao diện cho đẹp. Giờ bạn cần phải làm 1 cái tương tự như thế ở chức năng Danh sách phòng ban, nếu copy paste nguyên bộ code kia ra 1 cái mới và đổi tên đi để dùng lại (SearchBoxStaff, SearchBoxDepartment) thì là việc không nên chút nào. Bạn cần tái sử dụng các phần chung như giao diện, gợi ý lịch sử, tìm kiếm theo giọng nói. Và tách biệt các phần riêng như hàm xử lý tìm kiếm.

Vào đề hơi dài, hướng dẫn chi tiết đây:

Đầu tiên tạo 1 interface khai báo các sự kiện chung và các method cần dùng.

package net.tunghuynh.listener;
public interface SearchBoxListener {
    /**
     * Khai báo hàm xử lý tìm kiếm
     *
     * @param keyword Từ khóa cần tìm kiếm được nhập từ textbox
     */
    public void onSearch(String keyword);
}

Chỉnh sửa lại hàm xử lý tìm kiếm của SearchBox, chỉ gọi hàm sự kiện ở đây

package net.tunghuynh.listener;
public class SearchBoxController {
    private String txtKeyword;//Textbox nhập từ khóa tìm kiếm
    private SearchBoxListener searchBoxListener;//Listener xử lý sự kiện tìm kiếm
    //todo Khai báo các biến cần xử lý khác
    public void onClickButtonSearch() {
        if (searchBoxListener != null) {
            searchBoxListener.onSearch(txtKeyword);
        }
    }
    public void addSearchBoxListener(SearchBoxListener searchBoxListener){
        this.searchBoxListener = searchBoxListener;
    }
    //todo Định nghĩa các method xử lý (gợi ý lịch sử, tìm kiếm theo giọng nói, .....)
}

Như vậy là đã đóng gói xong SearchBox, giờ là cách sử dụng trong từng chức năng.
Danh sách nhân viên: (Ở đây mình dùng cách truyền thống là implement trên chính class hiện tại)

package net.tunghuynh.listener;
public class ListStaffController implements SearchBoxListener{
    private SearchBoxController searchBoxController;
    //todo Khai báo các biến cần xử lý khác
    public ListStaffController(){
        //todo Khởi tạo các giá trị ban đầu
        //Định nghĩa cách thực thi cho xử lý tìm kiếm đối với Danh sách nhân viên
        searchBoxController.addSearchBoxListener(this);
    }
    @Override
    public void onSearch(String keyword) {
        //Truy vấn bảng Nhân viên theo keyword để tìm nhân viên
        //Hiển thị danh sách nhân viên tìm được vào grid Nhân viên trên giao diện
    }
}

Danh sách phòng ban: (Ở đây mình dùng cách 2 là implement trực tiếp hàm xử lý dưới dạng Anynomous Class)

package net.tunghuynh.listener;
public class ListDepartmentController{
    private SearchBoxController searchBoxController;
    
    //todo Khai báo các biến cần xử lý khác
    
    public ListDepartmentController(){
        
        //todo Khởi tạo các giá trị ban đầu
        
        //Định nghĩa cách thực thi cho xử lý tìm kiếm đối với Danh sách phòng ban
        searchBoxController.addSearchBoxListener(new SearchBoxListener() {
            @Override
            public void onSearch(String keyword) {
                //Truy vấn bảng Phòng ban theo keyword để tìm phòng ban
                //Hiển thị danh sách phòng ban tìm được vào grid Phòng ban trên giao diện
            }
        });
    }
}

Tổng kết:
Như đã nói ở trên, sau khi hiểu được cách làm này thì bạn sẽ nắm được 2 nguyên lý trong SOLID
– Open/Closed:

  • Open: Bạn có thể sử dụng SearchBox này cho nhiều chức năng khác mặc dù nghiệp vụ khác nhau
  • Closed: Bạn đưa cho người khác sử dụng, nhưng họ không thể thay đổi được các xử lý chính của SearchBox VD như tính năng gợi nhớ lịch sử tìm kiếm, tìm kiếm theo giọng nói.
  • Dependency Inversion: Nghịch đảo phụ thuộc
  • Theo cách truyền thống, bạn phải tạo 2 SearchBox riêng biệt cho 2 chức năng, điều này làm tăng liên kết cứng giữa các class và kém linh động khi nâng cấp chức năng.
  • Áp dụng DI, các class kết nối với nhau phải qua interface, mỗi class sẽ tự implement interface để xử lý, cái này làm giảm coupling giữa các class.

Chúc các bạn thành công và ứng dụng được nhiều trong các trường hợp thực tế.

2 Replies to “[Java] Phần 1: Interface với cách tạo Listener lắng nghe sự kiện và Event kích hoạt sự kiện”

Bình luận