Observer: publisher-subscriber và loose coupling
Định nghĩa Observer, quan hệ một-nhiều, nguyên lý loose coupling và câu chuyện trạm thời tiết với ba bảng hiển thị.
Trung bìnhDesign pattern ~60 phút
Ôn lại trước khi học
Giới thiệu
Bài này học cách một đổi lan tới nhiều bên. Bạn tách trạm đo khỏi bảng hiển thị.
Kiến thức cần nhớ
Observer một-nhiều
- Một chủ đề đổi, nhiều người nghe cập nhật.
- Chủ đề còn gọi là nhà xuất bản.
- Người nghe còn gọi là người đăng ký.
- Ví dụ đời thường: kênh video ra clip, fan nhận tin.
Trạm thời tiết và 3 bảng
- Trạm giữ số đo, 3 bảng đăng ký nghe.
- Số đổi thì trạm báo, mỗi bảng tự vẽ lại.
- Trạm không biết bảng vẽ kiểu gì.
- Ví dụ đời thường: loa phường phát, ai nghe thì nghe.
Lỏng ràng buộc và vòng đời
- Hai bên biết về nhau càng ít càng tốt.
- Thêm bảng mới không sửa trạm cũ.
- Vòng đời: đăng ký, báo đổi, hủy đăng ký.
- Ví dụ đời thường: theo dõi rồi hủy theo dõi.
| Cách làm | Thêm bảng mới |
|---|---|
| Gọi trực tiếp | Mở trạm ra sửa |
| Hỏi liên tục | Tốn tài nguyên |
| Observer | Chỉ cần đăng ký |
Ví dụ
- Input: 3 bảng đã đăng ký, số mới về. Output: cả 3 cùng làm mới đúng số.
- Giải thích: trạm duyệt danh sách và báo từng người.
💻 Bài tập
Ngôn ngữ: C++17 (hoặc Java). Nộp vào day-009/.
Bài 1: Trạm thời tiết
struct Observer { virtual void update(float temp, float humidity, float pressure) = 0; virtual ~Observer() = default; };
struct Subject {
virtual void registerObserver(Observer *o) = 0;
virtual void removeObserver(Observer *o) = 0;
virtual void notifyObservers() = 0;
virtual ~Subject() = default;
};
| Lớp | Việc phải làm |
|---|---|
WeatherData : Subject | Giữ số đo. setMeasurements(t, h, p) lưu rồi gọi notifyObservers() |
CurrentConditionsDisplay | In "Hiện tại: 26.5C, 65% ẩm" |
StatisticsDisplay | Giữ min, max, trung bình nhiệt độ |
ForecastDisplay | Áp suất tăng: "Trời đẹp", giảm: "Sắp mưa", giữ nguyên: "Như cũ" |
Ràng buộc
WeatherDatakhông biết tên lớp display nào.- Đăng ký trùng một observer thì bỏ qua.
- Hủy đăng ký trong lúc đang
notifykhông được làm crash.
setMeasurements(26, 65, 1012) -> Current: 26C 65%; Stats: avg 26 min 26 max 26; Forecast: Trời đẹp
setMeasurements(28, 70, 1010) -> Stats: avg 27 min 26 max 28; Forecast: Sắp mưa
removeObserver(forecast); setMeasurements(25, 90, 1010) -> chỉ 2 bảng in
Bài 2: Đơn hàng gọi thẳng sang Observer
Code ban đầu:
void Order::complete() {
status = "done";
inventory.decrease(items);
mailer.sendReceipt(customerEmail);
analytics.track("order_done", id);
}
Yêu cầu: Order chỉ phát sự kiện OrderCompleted{id, items, email}. Ba module tự đăng ký. Thêm LoyaltyPoints (cộng 1 điểm mỗi 100.000đ) mà không sửa Order.
Bài 3: Phòng đấu giá (nâng cao)
Auction là subject. Bidder là observer, mỗi người có ngân sách tối đa và bước giá riêng.
- Khi có giá mới,
Auctionbáo mọi bidder trừ người vừa trả giá. - Bidder tự quyết: nếu
giá hiện tại + bước <= ngân sáchthì trả giá, ngược lại rút lui (hủy đăng ký). - Phiên kết thúc khi không ai trả giá thêm. In người thắng và giá.
Giá khởi điểm 100. An(ngân sách 300, bước 20), Bình(250, 50), Chi(180, 10)
-> In từng lượt trả giá, ai rút lui lúc nào, người thắng cuối cùng.
Tránh đệ quy vô hạn: bidder trả giá trong update sẽ kích hoạt notify mới. Dùng hàng đợi sự kiện hoặc vòng lặp theo lượt.
✅ Tiêu chí hoàn thành
- Test mẫu Bài 1 in đúng từng dòng.
- Bài 2: thêm
LoyaltyPointschỉ tạo file mới. - Bài 3 kết thúc đúng, không đệ quy vô hạn, không crash khi hủy đăng ký giữa chừng.
- Giải thích được "loose coupling" bằng ví dụ trong code của mình.
❓ Câu hỏi ôn tập
- Observer là gì, quan hệ mấy-nhiều?
- Trạm thời tiết gồm những ai?
- Lỏng ràng buộc nghĩa là gì?
- Đăng ký rồi hủy hoạt động thế nào?
🌕 Bạn đã hoàn thành ngày 9. Làm hết bài tập phía trên rồi sang ngày tiếp theo.