Menu
CoddyTech

Mẫu Observer

Thuộc phần Lập trình hướng đối tượng trong hành trình C++ của Coddy. Bài học 94 trên 104.

Mẫu thiết kế Observer pattern xác định mối quan hệ phụ thuộc một nhiều giữa các đối tượng: khi một đối tượng (đối tượng subject) thay đổi trạng thái, tất cả đối tượng phụ thuộc vào nó (các observers) sẽ tự động nhận thông báo. Mẫu này rất phù hợp với các hệ thống sự kiện, cập nhật giao diện người dùng hoặc mọi tình huống cần nhiều đối tượng phản ứng với các thay đổi.

Mẫu này gồm hai vai trò chính: Subject duy trì danh sách các observer và thông báo cho họ, còn Observers triển khai một giao diện cập nhật:

#include <iostream>
#include <vector>
#include <algorithm>

class Observer {
public:
    virtual void update(int value) = 0;
    virtual ~Observer() = default;
};

class Subject {
    std::vector<Observer*> observers;
    int state = 0;
    
public:
    void attach(Observer* obs) {
        observers.push_back(obs);
    }
    
    void detach(Observer* obs) {
        observers.erase(
            std::remove(observers.begin(), observers.end(), obs),
            observers.end());
    }
    
    void setState(int value) {
        state = value;
        notify();
    }
    
    void notify() {
        for (Observer* obs : observers) {
            obs->update(state);
        }
    }
};

class Display : public Observer {
    std::string name;
public:
    Display(const std::string& n) : name(n) {}
    void update(int value) override {
        std::cout << name << " received: " << value << "\n";
    }
};

int main() {
    Subject sensor;
    Display screen1("Screen1"), screen2("Screen2");
    
    sensor.attach(&screen1);
    sensor.attach(&screen2);
    sensor.setState(42);  // Cả hai màn hình đều nhận được thông báo
}

Khi gọi setState(), đối tượng chủ thể lần lượt gọi phương thức update() của tất cả đối tượng quan sát đã đăng ký. Bạn có thể thêm hoặc gỡ đối tượng quan sát bất cứ lúc nào, giúp hệ thống linh hoạt và ít phụ thuộc lẫn nhau.

Dùng Observer khi thay đổi ở một đối tượng cần cập nhật các đối tượng khác và bạn không muốn các đối tượng này phụ thuộc chặt chẽ vào nhau.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống giám sát trạm thời tiết bằng mẫu Observer. Bạn sẽ tạo một trạm thời tiết theo dõi dữ liệu nhiệt độ và tự động thông báo cho nhiều thiết bị hiển thị mỗi khi nhiệt độ thay đổi, một ứng dụng thực tế kinh điển của mẫu này.

Bạn sẽ sắp xếp code trong ba tệp:

  • Observer.h: Định nghĩa giao diện observer và một observer hiển thị cụ thể.

    Tạo lớp Observer trừu tượng với phương thức ảo thuần túy update(double temperature) để các observer triển khai và nhận thông báo, cùng với hàm hủy ảo.

    Sau đó, tạo lớp TemperatureDisplay kế thừa từ Observer. Mỗi thiết bị hiển thị có một tên (chuỗi) được thiết lập qua hàm khởi tạo. Khi gọi update(), phương thức này sẽ in ra:

    [DisplayName]: Temperature is [temperature] degrees
  • WeatherStation.h: Tạo đối tượng chủ thể lưu giữ nhiệt độ và thông báo cho các observer.

    Lớp WeatherStation cần:

    • Lưu danh sách con trỏ đến observer và nhiệt độ hiện tại (khởi tạo bằng 0.0)
    • Có phương thức attach(Observer* obs) để đăng ký observer
    • Có phương thức detach(Observer* obs) để xóa observer
    • Có phương thức setTemperature(double temp) để cập nhật nhiệt độ và thông báo cho tất cả observer đã đăng ký
    • Có phương thức riêng tư notify() gọi update() trên từng observer với nhiệt độ hiện tại
  • main.cpp: Minh họa cách mẫu Observer hoạt động.

    Đọc ba đầu vào:

    1. Tên thiết bị hiển thị thứ nhất (chuỗi)
    2. Tên thiết bị hiển thị thứ hai (chuỗi)
    3. Một giá trị nhiệt độ (số thực)

    Tạo một WeatherStation và hai đối tượng TemperatureDisplay với tên đã nhập. Gắn cả hai thiết bị hiển thị vào trạm, rồi đặt nhiệt độ bằng giá trị đầu vào. Cả hai thiết bị hiển thị sẽ tự động nhận thông báo cập nhật và in thông báo của mình.

    Sau đó, gỡ thiết bị hiển thị thứ nhất khỏi trạm và đặt nhiệt độ mới cao hơn giá trị đầu vào 5 độ. Chỉ thiết bị hiển thị thứ hai nhận được thông báo cập nhật này.

Ví dụ, với các đầu vào Kitchen, Bedroom và 22.5:

Kitchen: Temperature is 22.5 degrees
Bedroom: Temperature is 22.5 degrees
Bedroom: Temperature is 27.5 degrees

Với các đầu vào Office, Lobby và 18.0:

Office: Temperature is 18 degrees
Lobby: Temperature is 18 degrees
Lobby: Temperature is 23 degrees

Hãy chú ý cách mẫu Observer tạo ra một hệ thống ít phụ thuộc chặt chẽ: trạm thời tiết không cần biết gì về từng thiết bị hiển thị cụ thể, mà chỉ cần thông báo cho những thiết bị đang lắng nghe. Khi bạn gỡ một observer, observer đó sẽ tự động ngừng nhận thông báo cập nhật.

Tự làm thử

#include <iostream>
#include <string>
#include "WeatherStation.h"

using namespace std;

int main() {
    // Đọc dữ liệu đầu vào
    string display1Name, display2Name;
    double temperature;
    
    cin >> display1Name;
    cin >> display2Name;
    cin >> temperature;
    
    // TODO: Tạo một đối tượng WeatherStation
    
    // TODO: Tạo hai đối tượng TemperatureDisplay với các tên đầu vào
    
    // TODO: Gắn cả hai màn hình hiển thị vào trạm thời tiết
    
    // TODO: Đặt nhiệt độ bằng giá trị đầu vào
    // (Cả hai màn hình hiển thị đều phải in ra thông báo)
    
    // TODO: Tách màn hình hiển thị thứ nhất
    
    // TODO: Đặt nhiệt độ cao hơn đầu vào 5 độ
    // (Chỉ màn hình hiển thị thứ hai in ra thông báo)
    
    return 0;
}
quiz iconTự kiểm tra

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

Tự luyện tập: Trình biên dịch C++ online