Giao diện
🔀 Strategy Pattern Behavioral
"Has-A tốt hơn Is-A." Chuyển đổi thuật toán lúc runtime mà không cần tạo subclass mới.
Vấn đề Kế thừa (Lần nữa)
Ví dụ Sorting Engine
cpp
// ❌ CÁCH TIẾP CẬN KẾ THỪA
class Sorter {
public:
virtual void Sort(std::vector<int>& data) = 0;
};
class QuickSorter : public Sorter {
public:
void Sort(std::vector<int>& data) override { /* quicksort */ }
};
class MergeSorter : public Sorter {
public:
void Sort(std::vector<int>& data) override { /* mergesort */ }
};
class HeapSorter : public Sorter {
public:
void Sort(std::vector<int>& data) override { /* heapsort */ }
};
// Code client
std::unique_ptr<Sorter> sorter;
if (data.size() < 100) {
sorter = std::make_unique<QuickSorter>(); // Dữ liệu nhỏ
} else {
sorter = std::make_unique<MergeSorter>(); // Dữ liệu lớn
}
sorter->Sort(data);
// Không thể thay đổi thuật toán MÀ KHÔNG tạo object mới!⚠️ CÁC VẤN ĐỀ
- Thuật toán được CỐ ĐỊNH lúc khởi tạo
- Chuyển đổi yêu cầu tạo object mới
- Không thể kết hợp thuật toán dễ dàng
- Bùng nổ kiểu với các biến thể
Giải pháp Strategy Pattern
cpp
// ✅ STRATEGY PATTERN
// Bước 1: Định nghĩa Strategy Interface
class ISortStrategy {
public:
virtual ~ISortStrategy() = default;
virtual void Sort(std::vector<int>& data) = 0;
virtual std::string Name() const = 0;
};
// Bước 2: Implement các Concrete Strategies
class QuickSort : public ISortStrategy {
public:
void Sort(std::vector<int>& data) override {
// Implementation QuickSort
std::sort(data.begin(), data.end());
}
std::string Name() const override { return "QuickSort"; }
};
class MergeSort : public ISortStrategy {
public:
void Sort(std::vector<int>& data) override {
// Implementation MergeSort
std::stable_sort(data.begin(), data.end());
}
std::string Name() const override { return "MergeSort"; }
};
class HeapSort : public ISortStrategy {
public:
void Sort(std::vector<int>& data) override {
std::make_heap(data.begin(), data.end());
std::sort_heap(data.begin(), data.end());
}
std::string Name() const override { return "HeapSort"; }
};
// Bước 3: Lớp Context (Sử dụng strategy)
class SortingEngine {
public:
void SetStrategy(std::unique_ptr<ISortStrategy> strategy) {
strategy_ = std::move(strategy);
}
void Sort(std::vector<int>& data) {
if (!strategy_) {
throw std::runtime_error("Chưa thiết lập sorting strategy");
}
std::cout << "Đang sắp xếp với: " << strategy_->Name() << std::endl;
strategy_->Sort(data);
}
// Tự động chọn dựa trên dữ liệu
void AutoSort(std::vector<int>& data) {
if (data.size() < 50) {
SetStrategy(std::make_unique<QuickSort>());
} else if (NeedStability(data)) {
SetStrategy(std::make_unique<MergeSort>());
} else {
SetStrategy(std::make_unique<HeapSort>());
}
Sort(data);
}
private:
std::unique_ptr<ISortStrategy> strategy_;
bool NeedStability(const std::vector<int>&) { return false; }
};Cách sử dụng
cpp
SortingEngine engine;
std::vector<int> data = {5, 2, 8, 1, 9, 3};
// Chuyển đổi strategies lúc runtime!
engine.SetStrategy(std::make_unique<QuickSort>());
engine.Sort(data); // Đang sắp xếp với: QuickSort
engine.SetStrategy(std::make_unique<MergeSort>());
engine.Sort(data); // Đang sắp xếp với: MergeSort
// Hoặc để engine tự quyết định
engine.AutoSort(data);Sơ đồ Kiến trúc
┌─────────────────────────────────────────────────────────────────────────┐
│ KIẾN TRÚC STRATEGY PATTERN │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ SortingEngine │ │
│ │ ┌────────────────────────────────────────────────────────────┐ │ │
│ │ │ strategy_ : std::unique_ptr<ISortStrategy> │ │ │
│ │ └────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ «interface» ISortStrategy │ │
│ │ ┌───────────────────────┐ │ │
│ │ │ + Sort(data) │ │ │
│ │ │ + Name() : string │ │ │
│ │ └───────────┬───────────┘ │ │
│ └───────────────────────────────┼─────────────────────────────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ QuickSort │ │ MergeSort │ │ HeapSort │ │
│ │ │ │ │ │ │ │
│ │ + Sort() │ │ + Sort() │ │ + Sort() │ │
│ │ + Name() │ │ + Name() │ │ + Name() │ │
│ └───────────────┘ └───────────────┘ └───────────────┘ │
│ │
│ → Thêm thuật toán mới: chỉ tạo class mới implement ISortStrategy │
│ → Chuyển đổi runtime: engine.SetStrategy(new_strategy) │
│ → Open cho extension, closed cho modification │
│ │
└─────────────────────────────────────────────────────────────────────────┘Ví dụ Thực tế: Compression Engine
cpp
// Compression strategies
class ICompressionStrategy {
public:
virtual ~ICompressionStrategy() = default;
virtual std::vector<uint8_t> Compress(const std::vector<uint8_t>& data) = 0;
virtual std::vector<uint8_t> Decompress(const std::vector<uint8_t>& data) = 0;
virtual std::string Name() const = 0;
virtual int CompressionLevel() const = 0;
};
class ZstdCompression : public ICompressionStrategy {
public:
explicit ZstdCompression(int level = 3) : level_(level) {}
std::vector<uint8_t> Compress(const std::vector<uint8_t>& data) override {
// Sử dụng thư viện zstd
return ZSTD_compress(data, level_);
}
std::vector<uint8_t> Decompress(const std::vector<uint8_t>& data) override {
return ZSTD_decompress(data);
}
std::string Name() const override { return "Zstandard"; }
int CompressionLevel() const override { return level_; }
private:
int level_;
};
class LZ4Compression : public ICompressionStrategy {
public:
std::vector<uint8_t> Compress(const std::vector<uint8_t>& data) override {
return LZ4_compress(data);
}
std::vector<uint8_t> Decompress(const std::vector<uint8_t>& data) override {
return LZ4_decompress(data);
}
std::string Name() const override { return "LZ4"; }
int CompressionLevel() const override { return 1; } // Nhanh, nén ít hơn
};
class NoCompression : public ICompressionStrategy {
public:
std::vector<uint8_t> Compress(const std::vector<uint8_t>& data) override {
return data; // Không làm gì
}
std::vector<uint8_t> Decompress(const std::vector<uint8_t>& data) override {
return data;
}
std::string Name() const override { return "None"; }
int CompressionLevel() const override { return 0; }
};
// HPN Tunnel sử dụng strategy pattern
class HPNTunnel {
public:
HPNTunnel(std::unique_ptr<ICompressionStrategy> compression,
std::unique_ptr<IEncryptionStrategy> encryption)
: compression_(std::move(compression))
, encryption_(std::move(encryption)) {}
void Send(const std::vector<uint8_t>& data) {
auto compressed = compression_->Compress(data);
auto encrypted = encryption_->Encrypt(compressed);
DoSend(encrypted);
}
private:
std::unique_ptr<ICompressionStrategy> compression_;
std::unique_ptr<IEncryptionStrategy> encryption_;
};
// Tạo tunnel với strategies cụ thể
auto tunnel = std::make_unique<HPNTunnel>(
std::make_unique<ZstdCompression>(5), // Nén cao
std::make_unique<AES256GCMEncryption>() // Mã hóa mạnh
);
// Hoặc cho low-latency:
auto fast_tunnel = std::make_unique<HPNTunnel>(
std::make_unique<LZ4Compression>(), // Nén nhanh
std::make_unique<ChaCha20Encryption>() // Mã hóa nhanh
);Kết nối với Dependency Injection
Strategy Pattern + Factory = Code có thể Test được
cpp
// Factory tạo strategies dựa trên config
class TunnelFactory {
public:
static std::unique_ptr<ICompressionStrategy>
CreateCompression(const Config& config) {
if (config.compression == "zstd") {
return std::make_unique<ZstdCompression>(config.level);
} else if (config.compression == "lz4") {
return std::make_unique<LZ4Compression>();
} else {
return std::make_unique<NoCompression>();
}
}
};
// Trong production
auto tunnel = std::make_unique<HPNTunnel>(
TunnelFactory::CreateCompression(production_config),
TunnelFactory::CreateEncryption(production_config)
);
// Trong tests — inject mocks!
class MockCompression : public ICompressionStrategy {
public:
MOCK_METHOD(std::vector<uint8_t>, Compress,
(const std::vector<uint8_t>&), (override));
MOCK_METHOD(std::vector<uint8_t>, Decompress,
(const std::vector<uint8_t>&), (override));
MOCK_METHOD(std::string, Name, (), (const, override));
MOCK_METHOD(int, CompressionLevel, (), (const, override));
};
TEST(TunnelTest, NenTruocKhiGui) {
auto mock_compression = std::make_unique<MockCompression>();
EXPECT_CALL(*mock_compression, Compress(_))
.WillOnce(Return(std::vector<uint8_t>{1, 2, 3}));
HPNTunnel tunnel(std::move(mock_compression),
std::make_unique<NoEncryption>());
tunnel.Send({10, 20, 30, 40, 50});
}Tránh Chuỗi Switch/If
cpp
// ❌ TRƯỚC: Switch statement
void ProcessMessage(MessageType type, const Message& msg) {
switch (type) {
case MessageType::Text:
ProcessTextMessage(msg);
break;
case MessageType::Image:
ProcessImageMessage(msg);
break;
case MessageType::Video:
ProcessVideoMessage(msg);
break;
// Thêm type mới = sửa đổi switch này!
}
}
// ✅ SAU: Strategy Pattern
class IMessageProcessor {
public:
virtual void Process(const Message& msg) = 0;
};
class TextProcessor : public IMessageProcessor { ... };
class ImageProcessor : public IMessageProcessor { ... };
class VideoProcessor : public IMessageProcessor { ... };
std::map<MessageType, std::unique_ptr<IMessageProcessor>> processors;
processors[MessageType::Text] = std::make_unique<TextProcessor>();
processors[MessageType::Image] = std::make_unique<ImageProcessor>();
processors[MessageType::Video] = std::make_unique<VideoProcessor>();
// Sử dụng
processors[msg.type]->Process(msg);
// Thêm type mới = thêm vào map, không sửa đổi code!
processors[MessageType::Audio] = std::make_unique<AudioProcessor>();Thực hành Tốt nhất
┌─────────────────────────────────────────────────────────────────────────┐
│ THỰC HÀNH TỐT NHẤT STRATEGY PATTERN │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ✅ KHI NÀO SỬ DỤNG │
│ ─────────────── │
│ • Nhiều thuật toán cho cùng một task │
│ • Cần chuyển đổi behavior lúc runtime │
│ • Muốn tránh cây kế thừa │
│ • Cần testability (inject mocks) │
│ │
│ ❌ KHI NÀO KHÔNG NÊN SỬ DỤNG │
│ ───────────────── │
│ • Chỉ có một thuật toán (quá phức tạp) │
│ • Thuật toán không bao giờ thay đổi │
│ • Điều kiện đơn giản (chỉ dùng if) │
│ │
│ 💡 MẸO │
│ ──────── │
│ • Sử dụng Factory để tạo strategies từ config │
│ • Truyền strategy qua constructor (không phải setter) │
│ • Cân nhắc std::function cho strategies đơn giản │
│ • Kết hợp với Observer cho cập nhật động │
│ │
└─────────────────────────────────────────────────────────────────────────┘