Menu
CoddyTech

Mẫu Strategy

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 95 trên 104.

Mẫu Strategy pattern định nghĩa một nhóm thuật toán, đóng gói từng thuật toán và cho phép thay thế chúng cho nhau. Nhờ đó, bạn có thể thay đổi hành vi của một đối tượng trong lúc chương trình chạy mà không sửa code của đối tượng đó. Thuật toán thay đổi độc lập với các thành phần sử dụng nó.

Mẫu này gồm ba phần: giao diện Strategy khai báo phương thức thuật toán, các chiến lược cụ thể triển khai những biến thể khác nhau và ngữ cảnh sử dụng một chiến lược:

#include <iostream>
#include <memory>

// Giao diện chiến lược
class PaymentStrategy {
public:
    virtual void pay(int amount) = 0;
    virtual ~PaymentStrategy() = default;
};

// Các chiến lược cụ thể
class CreditCardPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " via Credit Card\n";
    }
};

class PayPalPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " via PayPal\n";
    }
};

// Ngữ cảnh
class ShoppingCart {
    std::unique_ptr<PaymentStrategy> strategy;
public:
    void setPaymentMethod(std::unique_ptr<PaymentStrategy> s) {
        strategy = std::move(s);
    }
    
    void checkout(int total) {
        if (strategy) strategy->pay(total);
    }
};

int main() {
    ShoppingCart cart;
    
    cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
    cart.checkout(100);
    
    cart.setPaymentMethod(std::make_unique<PayPalPayment>());
    cart.checkout(50);
}

ShoppingCart không biết mình đang dùng phương thức thanh toán nào, nó chỉ gọi pay() trên chiến lược đã được thiết lập. Bạn có thể thay đổi chiến lược trong khi chương trình đang chạy bằng setPaymentMethod(), giúp hệ thống linh hoạt và dễ mở rộng với các lựa chọn thanh toán mới.

Dùng Strategy khi bạn có nhiều thuật toán cho một tác vụ cụ thể và muốn chuyển đổi linh hoạt giữa chúng, hoặc khi muốn tránh dùng câu lệnh điều kiện (conditional statement) để chọn hành vi.

challenge icon

Thử thách

Dễ

Hãy xây dựng một trình tính phí vận chuyển sử dụng mẫu Strategy để tính chi phí giao hàng theo các phương thức vận chuyển khác nhau. Đây là tình huống thực tế cần thay đổi thuật toán trong thời gian chạy: cùng một kiện hàng có thể được vận chuyển bằng đường bộ, đường hàng không hoặc chuyển phát nhanh, mỗi phương thức có cách tính giá riêng.

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

  • ShippingStrategy.h: Định nghĩa giao diện chiến lược và các chiến lược vận chuyển cụ thể.

    Tạo lớp trừu tượng ShippingStrategy với phương thức ảo thuần túy calculateCost(double weight), trả về chi phí vận chuyển dưới dạng số thực, cùng với hàm hủy ảo.

    Sau đó, triển khai ba chiến lược cụ thể:

    • GroundShipping: chi phí 1.5 cho mỗi đơn vị trọng lượng (weight * 1.5)
    • AirShipping: chi phí 4.0 cho mỗi đơn vị trọng lượng (weight * 4.0)
    • ExpressShipping: chi phí 6.5 cho mỗi đơn vị trọng lượng, cộng thêm phí cố định 10.0 (weight * 6.5 + 10.0)
  • ShippingService.h: Tạo lớp ngữ cảnh sử dụng một chiến lược vận chuyển.

    Lớp ShippingService cần lưu một std::unique_ptr<ShippingStrategy> làm thành viên riêng tư. Triển khai:

    • Phương thức setStrategy(std::unique_ptr<ShippingStrategy> strategy) để thay đổi phương thức vận chuyển
    • Phương thức calculateShipping(double weight) sử dụng chiến lược hiện tại để tính và trả về chi phí

    Nếu chưa đặt chiến lược khi gọi calculateShipping, hãy trả về 0.0.

  • main.cpp: Minh họa việc thay đổi chiến lược trong thời gian chạy.

    Đọc hai đầu vào:

    1. Trọng lượng kiện hàng (số thực)
    2. Phương thức vận chuyển: ground, air hoặc express

    Tạo một ShippingService và đặt chiến lược phù hợp dựa trên phương thức đầu vào. Tính và in ra chi phí vận chuyển.

    Sau đó, chuyển sang một chiến lược khác (dùng air nếu đầu vào không phải air, nếu không thì dùng ground) và tính lại chi phí với cùng trọng lượng. Qua đó, bạn sẽ thấy sức mạnh của việc thay đổi chiến lược trong thời gian chạy.

    In mỗi chi phí trên một dòng riêng, với chính xác một chữ số thập phân và có tên phương thức ở phía trước:

    [Method]: $[cost]

Ví dụ, với đầu vào 5.0 và ground:

Ground: $7.5
Air: $20.0

Với đầu vào 3.0 và express:

Express: $29.5
Air: $12.0

Với đầu vào 10.0 và air:

Air: $40.0
Ground: $15.0

Lưu ý rằng ShippingService không cần biết chi tiết của từng thuật toán tính giá. Lớp này chỉ chuyển việc tính toán cho chiến lược hiện được đặt. Bạn có thể dễ dàng thêm phương thức vận chuyển mới (như giao hàng bằng drone hoặc giao trong ngày) mà không cần sửa đổi lớp dịch vụ.

Tự làm thử

#include <iostream>
#include <string>
#include <iomanip>
#include <memory>
#include "ShippingStrategy.h"
#include "ShippingService.h"

int main() {
    double weight;
    std::string method;
    
    std::cin >> weight;
    std::cin >> method;
    
    // Đặt đầu ra có 1 chữ số thập phân
    std::cout << std::fixed << std::setprecision(1);
    
    ShippingService service;
    
    // TODO: Dựa vào phương thức đầu vào ("ground", "air" hoặc "express"):
    // 1. Thiết lập chiến lược phù hợp cho service
    // 2. Tính và in chi phí theo định dạng: "[Method]: $[cost]"
    
    // TODO: Chuyển sang chiến lược khác:
    // Nếu đầu vào là "air", chuyển sang GroundShipping
    // Nếu không, chuyển sang AirShipping
    // Tính và in chi phí mới
    
    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