Mẫu Observer
Thuộc phần Lập trình hướng đối tượng trong hành trình Java của Coddy. Bài học 68 trên 87.
Mẫu Observer là một mẫu thiết kế hành vi thiết lập mối quan hệ một nhiều giữa các đối tượng. Khi một đối tượng (đối tượng chủ thể) thay đổi trạng thái, tất cả đối tượng phụ thuộc vào nó (các đối tượng quan sát) sẽ tự động nhận thông báo và cập nhật. Hãy hình dung như đăng ký nhận bản tin: người đăng ký nhận thông tin cập nhật mỗi khi nội dung mới được đăng.
Mẫu này gồm hai thành phần chính: một đối tượng duy trì danh sách các đối tượng quan sát và thông báo cho chúng khi có thay đổi, cùng các đối tượng quan sát định nghĩa phương thức cập nhật để phản hồi thông báo:
import java.util.ArrayList;
import java.util.List;
interface Observer {
void update(String message);
}
class NewsPublisher {
private List<Observer> subscribers = new ArrayList<>();
public void subscribe(Observer observer) {
subscribers.add(observer);
}
public void unsubscribe(Observer observer) {
subscribers.remove(observer);
}
public void publish(String news) {
for (Observer subscriber : subscribers) {
subscriber.update(news);
}
}
}
class EmailSubscriber implements Observer {
private String email;
public EmailSubscriber(String email) {
this.email = email;
}
public void update(String message) {
System.out.println("Email to " + email + ": " + message);
}
}Khi nhà phát hành đăng tin, tất cả người đăng ký sẽ tự động nhận được thông báo:
NewsPublisher publisher = new NewsPublisher();
publisher.subscribe(new EmailSubscriber("alice@mail.com"));
publisher.subscribe(new EmailSubscriber("bob@mail.com"));
publisher.publish("Breaking news!");
// Đầu ra:
// Email to alice@mail.com: Breaking news!
// Email to bob@mail.com: Breaking news!Mẫu thiết kế Observer giúp giảm sự phụ thuộc chặt chẽ: đối tượng chủ thể không cần biết các lớp cụ thể của các đối tượng quan sát, mà chỉ cần biết chúng triển khai giao diện Observer. Nhờ vậy, bạn có thể dễ dàng thêm các kiểu đối tượng quan sát mới mà không cần sửa đổi đối tượng chủ thể.
Thử thách
DễHãy xây dựng một hệ thống cảnh báo giá cổ phiếu bằng Mẫu thiết kế Observer! Bạn sẽ tạo một hệ thống trong đó bộ theo dõi cổ phiếu thông báo cho nhiều nhà đầu tư mỗi khi giá cổ phiếu thay đổi, để mỗi nhà đầu tư có thể phản ứng theo cách riêng khi giá cập nhật.
Bạn sẽ sắp xếp code trong bốn tệp:
Observer.java: Định nghĩa giao diệnObserverđể tất cả nhà đầu tư triển khai. Giao diện cần khai báo một phương thức duy nhất làupdate(String stockName, double price), cho phép các observer nhận thông báo khi giá thay đổi.StockTicker.java: Tạo lớp chủ thể lưu danh sách observer và thông báo cho họ khi giá thay đổi.StockTickercần có:Một trường String riêng tư lưu tên cổ phiếu, được thiết lập qua hàm khởi tạo.
Một trường double riêng tư lưu giá hiện tại, ban đầu là 0.
Một danh sách riêng tư để lưu các observer đã đăng ký.
Một phương thức
subscribe(Observer observer)để thêm observer vào danh sách.Một phương thức
unsubscribe(Observer observer)để xóa observer khỏi danh sách.Một phương thức
setPrice(double price)để cập nhật giá, sau đó thông báo cho tất cả observer bằng cách gọi phương thứcupdatecủa họ với tên cổ phiếu và giá mới.Investors.java: Tạo hai kiểu nhà đầu tư khác nhau triển khai giao diệnObserver:DayTrader: có một trường name riêng tư được thiết lập qua hàm khởi tạo. Khi nhận cập nhật, in ra[name] received alert: [stockName] is now $[price] - Making quick trade!LongTermInvestor: có một trường name riêng tư được thiết lập qua hàm khởi tạo. Khi nhận cập nhật, in ra[name] received alert: [stockName] is now $[price] - Holding steady.Định dạng giá với hai chữ số thập phân.
Main.java: Kết hợp hệ thống observer của bạn! Bạn sẽ nhận ba đầu vào: tên cổ phiếu (String), lần cập nhật giá thứ nhất (double) và lần cập nhật giá thứ hai (double).Tạo một
StockTickervới tên cổ phiếu đã cho. Tạo mộtDayTradercó tên"Alice"và mộtLongTermInvestorcó tên"Bob", rồi đăng ký cả hai với bộ theo dõi.Đặt giá thành giá trị đầu vào thứ nhất (tất cả observer đều nhận thông báo). Sau đó hủy đăng ký Alice. Cuối cùng, đặt giá thành giá trị đầu vào thứ hai (lần này chỉ Bob nhận thông báo).
Bạn sẽ nhận ba đầu vào theo thứ tự: tên cổ phiếu (String), giá thứ nhất (double), giá thứ hai (double).
Thử thách này cho thấy sức mạnh của Mẫu thiết kế Observer: StockTicker không cần biết gì về từng kiểu nhà đầu tư cụ thể. Nó chỉ cần biết họ triển khai Observer. Khi bạn hủy đăng ký Alice, cô ấy sẽ ngừng nhận cập nhật, còn Bob vẫn tiếp tục nhận thông báo. Cách ghép nối lỏng lẻo này giúp bạn dễ dàng thêm kiểu nhà đầu tư mới mà không cần thay đổi bộ theo dõi!
Tự làm thử
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Đọc dữ liệu đầu vào
String stockName = scanner.nextLine();
double firstPrice = scanner.nextDouble();
double secondPrice = scanner.nextDouble();
// TODO: Tạo StockTicker với tên cổ phiếu đã cho
// TODO: Tạo DayTrader có tên "Alice"
// TODO: Tạo LongTermInvestor có tên "Bob"
// TODO: Đăng ký cho cả Alice và Bob theo dõi ticker
// TODO: Đặt giá thành firstPrice (cả hai đối tượng theo dõi đều nhận thông báo)
// TODO: Hủy đăng ký theo dõi cho Alice
// TODO: Đặt giá thành secondPrice (chỉ Bob nhận thông báo)
scanner.close();
}
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Kiến thức cơ bản về lập trình hướng đối tượng
Tệp bên ngoàiGiới thiệu về lập trình hướng đối tượngLớp và đối tượngTừ khóa thisPhương thứcTrường (thuộc tính)Phương thức khởi tạoNạp chồng phương thức khởi tạoÔn tập: Máy tính đơn giản4Kế thừa
Kế thừa cơ bản (extends)Từ khóa superGhi đè phương thức (@Override)Chuỗi gọi hàm khởi tạoLớp ObjectKế thừa đơn và đa cấpVì sao không hỗ trợ kế thừa nhiều lớpÔn tập: Phân cấp lớp Employee7Phương thức đặc biệt và lớp Object
Phương thức toString()equals() và hashCode()Phương thức clone()compareTo() và ComparableGiao diện ComparatorÔn tập: sắp xếp tùy chỉnh2Bộ điều chỉnh quyền truy cập và đóng gói
Tổng quan về các mức truy cậpPhương thức getter và setterẨn thông tinTừ khóa finalÔn tập: Trình quản lý tài khoản ngân hàng5Tính đa hình
Kiến thức cơ bản về nạp chồng phương thứcGhi đè phương thức (thời gian chạy)Chuyển kiểu lên và chuyển kiểu xuốngToán tử instanceofLớp và phương thức trừu tượngÔn tập: máy tính hình học8Khái niệm OOP nâng cao
Kết hợp và kế thừaKết tập và hợp thànhLớp bên trong, lớp lồng nhau và lớp ẩn danhKiểu enum và phương thức enumBản ghi (Java 16+)Lớp sealed (Java 17+)11Mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếMẫu SingletonMẫu FactoryMẫu BuilderMẫu ObserverMẫu Strategy3Thuộc tính lớp và thành viên tĩnh
Biến của instance và biến tĩnhPhương thức tĩnhKhối tĩnhHằng số (static final)Ôn tập: bộ đếm và tiện ích6Giao diện và lớp trừu tượng
Giới thiệu về giao diệnTriển khai giao diệnTriển khai nhiều giao diệnPhương thức mặc định và tĩnh trong giao diệnLớp trừu tượng và giao diệnGiao diện hàmÔn tập: hệ thống thanh toán9Kiểu tổng quát
Giới thiệu về kiểu tổng quátLớp tổng quátPhương thức tổng quátTham số kiểu có giới hạnKý tự đại diện (?, extends, super)Ôn tập: vùng chứa tổng quát12Mẫu thiết kế, phần 2
Mẫu CommandMẫu AdapterMẫu DecoratorMẫu Template MethodMẫu StateMẫu CompositeMẫu IteratorTự luyện tập: Trình biên dịch Java online