Giao diện
📡 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 ĐỀ
- Module Core phụ thuộc vào module UI (rủi ro circular dependency)
- Thêm listener mới yêu cầu sửa đổi Core
- Không thể chạy Core mà không có UI (testing không thể)
- 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.zipCâ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) │
│ │
└─────────────────────────────────────────────────────────────────────────┘