Skip to content

🔀 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                              │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

🎉 Module Hoàn thành!

Bạn đã học toàn bộ về Design Patterns:

  1. 🧱 Pimpl Idiom (Ổn định ABI)
  2. 🔒 Singleton (Thread-safe với Meyers')
  3. 📡 Observer (Pub/Sub, Event-driven)
  4. 🔀 Strategy (Tổ hợp thay vì Kế thừa)

Bước tiếp theo: Quay lại C++ Roadmap để tiếp tục học các modules khác!