Observer
Nội dung bài · 5 mục
- 1.Khái niệm
- 2.Ví dụ
- 3.Thử ngay
- 4.Lỗi hay gặp
- 5.Tóm tắt
Đặt hàng xong phải gửi email, cộng điểm thành viên, gửi SMS. Nếu
OrderService tự gọi cả ba, mỗi việc mới lại phải sửa OrderService, và nó
phải biết mọi phần khác của hệ thống. Với Observer, OrderService chỉ thông
báo "đã đặt hàng", việc nào cần thì tự đăng ký nghe.
Khái niệm
📣 Observer: một object phát thông báo khi có việc xảy ra, các object khác đăng ký nghe và tự xử lý, bên phát không cần biết ai đang nghe.
Java không có từ khoá riêng cho việc này. Bên nghe là một listener: object implement một interface do bên phát đặt ra. Bên phát giữ danh sách listener và gọi lần lượt từng cái.
Ví dụ
// Main.java
void main() {
var service = new OrderService();
service.addListener(code ->
System.out.println("Gửi email cho " + code));
service.addListener(code ->
System.out.println("Cộng điểm cho " + code));
service.place("DH1");
}
// OrderPlacedListener.java
interface OrderPlacedListener {
void onOrderPlaced(String code);
}
// OrderService.java
import java.util.*;
class OrderService {
private final List<OrderPlacedListener> listeners =
new ArrayList<>();
public void addListener(OrderPlacedListener l) {
listeners.add(l);
}
public void place(String code) {
System.out.println("Đã lưu " + code);
for (OrderPlacedListener l : listeners) {
l.onOrderPlaced(code);
}
}
}OrderPlacedListenerlà interface chỉ có một method, nhận mã đơn. Interface như vậy viết object implement nó gọn bằng lambda, nhưFunctionở bài Lambda.placegọi lần lượt mọi listener trong danh sách, theo thứ tự đăng ký.OrderServiceở đây là bản rút gọn, chỉ giữ phần thông báo. Nó không biết gì về email hay điểm. Thêm việc gửi SMS là thêm mộtaddListenerở ngoài.
Thử ngay
Chạy ví dụ, rồi thêm hai dòng sau vào cuối main:
var lonely = new OrderService();
lonely.place("DH2");Đoán trước khi chạy: toàn bộ chương trình in ra những dòng nào? Đơn DH2 không có ai nghe thì có lỗi không?
Xem kết quả
Đã lưu DH1
Gửi email cho DH1
Cộng điểm cho DH1
Đã lưu DH2Listener chạy theo thứ tự đăng ký. lonely có danh sách listener rỗng, vòng
for không chạy lần nào nên không lỗi.
Lỗi hay gặp
Quên tạo danh sách listener. Field chưa gán thì là null, chưa ai đăng
ký mà gọi place là ném NullPointerException.
// SAI — không có listener là NullPointerException
// OrderService.java
import java.util.*;
class OrderService {
private List<OrderPlacedListener> listeners;
public void place(String code) {
for (OrderPlacedListener l : listeners) {
l.onOrderPlaced(code);
}
}
}// ĐÚNG — danh sách rỗng có sẵn từ đầu
// OrderService.java
import java.util.*;
class OrderService {
private final List<OrderPlacedListener> listeners =
new ArrayList<>();
public void place(String code) {
for (OrderPlacedListener l : listeners) {
l.onOrderPlaced(code);
}
}
}Tóm tắt
- Observer: bên phát thông báo, nhiều bên nghe tự phản ứng.
- Trong Java, bên nghe implement một interface listener, đăng ký bằng
addListener. - Bên phát không biết bên nghe, thêm việc mới không phải sửa bên phát.
- Tạo sẵn danh sách listener rỗng để không lỗi khi chưa ai nghe.
- Giữa các bean, Spring có sẵn
ApplicationEventPublishervà@EventListenerlàm đúng việc này.
Tự kiểm tra
0/3 câuThêm việc "gửi SMS khi đặt hàng" với Observer. Cần sửa OrderService không?
Listener gửi email đăng ký trước và ném exception. Listener cộng điểm đăng ký sau nó có chạy không?
place gọi các listener trước dòng lưu đơn vào database. Rủi ro là gì?