Skip to content

📡 Observer Pattern Behavioral

Tách rời sender khỏi receiver. HPN Core thông báo UI khi download hoàn thành — mà không cần biết UI là gì.

Vấn đề Coupling

Trước: Tightly Coupled

cpp
// ❌ TIGHTLY COUPLED — Core biết về UI!
class DownloadCore {
public:
    void StartDownload(const std::string& url) {
        // ... logic download ...
        
        // VẤN ĐỀ: Core phụ thuộc vào các lớp UI cụ thể!
        main_window_->UpdateProgress(progress);
        status_bar_->SetText("Đang tải...");
        notification_popup_->Show("Download hoàn thành");
        
        // Nếu chúng ta muốn thêm listener khác thì sao?
        // Nếu UI ở module khác thì sao?
        // Nếu chúng ta muốn chế độ console-only thì sao?
    }

private:
    MainWindow* main_window_;      // Phụ thuộc UI!
    StatusBar* status_bar_;        // Phụ thuộc UI!
    NotificationPopup* notification_popup_;  // Phụ thuộc UI!
};

💀 CÁC VẤN ĐỀ

  1. Module Core phụ thuộc vào module UI (rủi ro circular dependency)
  2. Thêm listener mới yêu cầu sửa đổi Core
  3. Không thể chạy Core mà không có UI (testing không thể)
  4. Vi phạm Nguyên tắc Open/Closed

Sau: Observer Pattern

cpp
// ✅ TÁCH RỜI — Core không biết ai đang lắng nghe
class DownloadCore {
public:
    using ProgressCallback = std::function<void(int percent)>;
    using CompleteCallback = std::function<void(const std::string& path)>;
    
    void OnProgress(ProgressCallback callback) {
        progress_callbacks_.push_back(std::move(callback));
    }
    
    void OnComplete(CompleteCallback callback) {
        complete_callbacks_.push_back(std::move(callback));
    }
    
    void StartDownload(const std::string& url) {
        // ... logic download ...
        
        // Thông báo TẤT CẢ listeners (bất kể họ là ai)
        for (auto& callback : progress_callbacks_) {
            callback(progress);
        }
        
        // ... khi hoàn thành ...
        for (auto& callback : complete_callbacks_) {
            callback(downloaded_path);
        }
    }

private:
    std::vector<ProgressCallback> progress_callbacks_;
    std::vector<CompleteCallback> complete_callbacks_;
};

// Bất kỳ đâu trong codebase:
core.OnProgress([&window](int p) { window.UpdateProgress(p); });
core.OnComplete([](const std::string& path) { 
    Logger::Info("Đã tải: " + path); 
});

Tổng quan Kiến trúc

┌─────────────────────────────────────────────────────────────────────────┐
│                    KIẾN TRÚC OBSERVER PATTERN                           │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   TRƯỚC (Tightly Coupled):                                              │
│   ─────────────────────────                                             │
│   ┌──────────────┐      ┌──────────────┐                               │
│   │ DownloadCore │─────►│  MainWindow  │                               │
│   │              │─────►│  StatusBar   │                               │
│   │              │─────►│  Popup       │                               │
│   └──────────────┘      └──────────────┘                               │
│   Core PHỤ THUỘC vào UI!                                                │
│                                                                         │
│   SAU (Tách rời):                                                       │
│   ──────────────────                                                    │
│   ┌──────────────┐                                                     │
│   │ DownloadCore │                                                     │
│   │  (Subject)   │                                                     │
│   │              │──► notify() ──┬──► MainWindow                       │
│   │              │               ├──► StatusBar                        │
│   │              │               ├──► Logger                           │
│   └──────────────┘               └──► ??? (listeners tương lai)        │
│                                                                         │
│   Core KHÔNG BIẾT GÌ về listeners!                                      │
│   Listeners subscribe lúc runtime.                                      │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Implementation Hoàn chỉnh

Lớp Event Emitter

cpp
// event_emitter.hpp
#pragma once
#include <functional>
#include <vector>
#include <mutex>
#include <algorithm>

template <typename... Args>
class EventEmitter {
public:
    using Callback = std::function<void(Args...)>;
    using CallbackId = size_t;
    
    // Subscribe sự kiện
    CallbackId Subscribe(Callback callback) {
        std::lock_guard<std::mutex> lock(mutex_);
        CallbackId id = next_id_++;
        callbacks_.emplace_back(id, std::move(callback));
        return id;
    }
    
    // Unsubscribe theo ID
    void Unsubscribe(CallbackId id) {
        std::lock_guard<std::mutex> lock(mutex_);
        callbacks_.erase(
            std::remove_if(callbacks_.begin(), callbacks_.end(),
                [id](const auto& pair) { return pair.first == id; }),
            callbacks_.end()
        );
    }
    
    // Emit sự kiện tới tất cả subscribers
    void Emit(Args... args) {
        // Copy callbacks để tránh giữ lock trong khi gọi callbacks
        std::vector<Callback> callbacks_copy;
        {
            std::lock_guard<std::mutex> lock(mutex_);
            callbacks_copy.reserve(callbacks_.size());
            for (const auto& [id, callback] : callbacks_) {
                callbacks_copy.push_back(callback);
            }
        }
        
        for (const auto& callback : callbacks_copy) {
            callback(args...);
        }
    }
    
    // Xóa tất cả subscribers
    void Clear() {
        std::lock_guard<std::mutex> lock(mutex_);
        callbacks_.clear();
    }
    
    size_t SubscriberCount() const {
        std::lock_guard<std::mutex> lock(mutex_);
        return callbacks_.size();
    }

private:
    mutable std::mutex mutex_;
    std::vector<std::pair<CallbackId, Callback>> callbacks_;
    CallbackId next_id_ = 0;
};

Ví dụ Sử dụng: Download Manager

cpp
// download_manager.hpp
#pragma once
#include "event_emitter.hpp"
#include <string>

struct DownloadProgress {
    std::string url;
    int percent;
    size_t downloaded_bytes;
    size_t total_bytes;
};

struct DownloadResult {
    std::string url;
    std::string file_path;
    bool success;
    std::string error_message;
};

class DownloadManager {
public:
    // Các sự kiện
    EventEmitter<DownloadProgress> OnProgress;
    EventEmitter<DownloadResult> OnComplete;
    EventEmitter<std::string> OnError;
    
    void StartDownload(const std::string& url) {
        // Giả lập download
        for (int i = 0; i <= 100; i += 10) {
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            
            OnProgress.Emit(DownloadProgress{
                .url = url,
                .percent = i,
                .downloaded_bytes = i * 1024,
                .total_bytes = 100 * 1024
            });
        }
        
        OnComplete.Emit(DownloadResult{
            .url = url,
            .file_path = "/downloads/file.zip",
            .success = true
        });
    }
};

Ví dụ Subscriber

cpp
// main.cpp
#include "download_manager.hpp"
#include <iostream>

int main() {
    DownloadManager manager;
    
    // UI subscribes
    auto progressId = manager.OnProgress.Subscribe(
        [](const DownloadProgress& p) {
            std::cout << "\r[" << std::string(p.percent / 10, '=') 
                      << std::string(10 - p.percent / 10, ' ') << "] "
                      << p.percent << "%" << std::flush;
        }
    );
    
    // Logger subscribes
    manager.OnComplete.Subscribe(
        [](const DownloadResult& r) {
            std::cout << "\n✅ Đã tải: " << r.file_path << std::endl;
        }
    );
    
    // Analytics subscribes
    manager.OnComplete.Subscribe(
        [](const DownloadResult& r) {
            // Gửi tới analytics server
            Analytics::Track("download_complete", {{"url", r.url}});
        }
    );
    
    manager.StartDownload("https://example.com/file.zip");
    
    // Unsubscribe khi xong
    manager.OnProgress.Unsubscribe(progressId);
    
    return 0;
}
)}

Kết quả:

[==========] 100%
✅ Đã tải: /downloads/file.zip

Cân nhắc Thread-Safety

┌─────────────────────────────────────────────────────────────────────────┐
│                    THREAD-SAFETY TRONG OBSERVER                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   VẤN ĐỀ: Điều gì xảy ra nếu...                                         │
│   ────────────────────                                                  │
│   • Thread A gọi Emit() trong khi Thread B gọi Unsubscribe()?           │
│   • Callback sửa đổi danh sách subscriber?                              │
│                                                                         │
│   GIẢI PHÁP CỦA CHÚNG TA:                                               │
│   ──────────────                                                        │
│   1. Lock mutex khi truy cập callbacks_                                 │
│   2. Copy callbacks trước khi emit (giải phóng lock)                    │
│   3. Gọi callbacks mà không giữ lock                                    │
│                                                                         │
│   void Emit(Args... args) {                                             │
│       std::vector<Callback> copy;                                       │
│       {                                                                 │
│           std::lock_guard lock(mutex_);  // Lock                        │
│           copy = callbacks_;              // Copy                       │
│       }                                   // Unlock                     │
│                                                                         │
│       for (auto& cb : copy) {                                           │
│           cb(args...);  // Không giữ lock, an toàn!                     │
│       }                                                                 │
│   }                                                                     │
│                                                                         │
│   → Callbacks có thể an toàn subscribe/unsubscribe trong Emit           │
│   → Không rủi ro deadlock                                               │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Ứng dụng HPN Core

cpp
// HPN Tunnel Core → Thông báo UI
class HPNTunnelCore {
public:
    EventEmitter<int> OnBandwidthUpdate;      // Mbps
    EventEmitter<std::string> OnStatusChange; // "Connected", "Reconnecting"
    EventEmitter<double, double> OnLatency;   // min, avg

    void Run() {
        while (running_) {
            // ... logic tunnel ...
            
            OnBandwidthUpdate.Emit(current_bandwidth);
            OnStatusChange.Emit(current_status);
            OnLatency.Emit(min_latency, avg_latency);
        }
    }
};

// Trong Qt UI:
connect_button.clicked([&]() {
    core.OnStatusChange.Subscribe([&](const std::string& status) {
        QMetaObject::invokeMethod(&status_label, [=]() {
            status_label.setText(QString::fromStdString(status));
        });
    });
    
    core.OnBandwidthUpdate.Subscribe([&](int mbps) {
        QMetaObject::invokeMethod(&bandwidth_chart, [=]() {
            bandwidth_chart.addPoint(mbps);
        });
    });
    
    core.Start();
});

Thực hành Tốt nhất

┌─────────────────────────────────────────────────────────────────────────┐
│                    THỰC HÀNH TỐT NHẤT OBSERVER                          │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   ✅ NÊN LÀM                                                            │
│   ─────                                                                 │
│   • Sử dụng std::function cho sự linh hoạt                              │
│   • Trả về subscription ID để unsubscribe                               │
│   • Copy callbacks trước khi emit (thread-safety)                       │
│   • Sử dụng weak_ptr nếu observer có thể bị hủy                         │
│                                                                         │
│   ❌ KHÔNG NÊN LÀM                                                      │
│   ───────                                                               │
│   • Đừng giữ locks trong khi gọi callbacks                              │
│   • Đừng giả định thứ tự callback                                       │
│   • Đừng throw exceptions từ callbacks                                  │
│   • Đừng tạo chuỗi notification vòng tròn                               │
│                                                                         │
│   💡 PHƯƠNG ÁN THAY THẾ                                                 │
│   ─────────────────                                                     │
│   • Qt Signals/Slots (nếu dùng Qt)                                      │
│   • Boost.Signals2 (nhiều tính năng hơn)                                │
│   • std::observer_ptr (C++experimental)                                 │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Bước tiếp theo

🔀 Strategy → — Tổ hợp thay vì Kế thừa