Menu
CoddyTech

Mẫu Strategy

Thuộc phần Lập trình hướng đối tượng trong hành trình PHP của Coddy. Bài học 74 trên 91.

Mẫu chiến lược (Strategy Pattern) là một mẫu thiết kế hành vi cho phép bạn xác định một họ thuật toán, đóng gói từng thuật toán trong lớp riêng và thay thế chúng cho nhau khi chương trình chạy. Đối tượng sử dụng chiến lược không cần biết mình đang chạy thuật toán nào.

Hãy tưởng tượng một giỏ hàng có thể áp dụng nhiều chiến lược giảm giá khác nhau. Thay vì viết cứng logic giảm giá bằng các câu lệnh điều kiện, bạn định nghĩa mỗi kiểu giảm giá thành một lớp riêng:

<?php
interface DiscountStrategy {
    public function calculate(float $total): float;
}

class NoDiscount implements DiscountStrategy {
    public function calculate(float $total): float {
        return $total;
    }
}

class PercentageDiscount implements DiscountStrategy {
    public function __construct(private int $percent) {}
    
    public function calculate(float $total): float {
        return $total - ($total * $this->percent / 100);
    }
}

class FixedDiscount implements DiscountStrategy {
    public function __construct(private float $amount) {}
    
    public function calculate(float $total): float {
        return max(0, $total - $this->amount);
    }
}

Lớp ngữ cảnh chấp nhận mọi chiến lược và ủy quyền việc tính toán:

<?php
class ShoppingCart {
    public function __construct(private DiscountStrategy $discount) {}
    
    public function setDiscount(DiscountStrategy $discount): void {
        $this->discount = $discount;
    }
    
    public function checkout(float $total): float {
        return $this->discount->calculate($total);
    }
}

$cart = new ShoppingCart(new NoDiscount());
echo $cart->checkout(100) . "\n";

$cart->setDiscount(new PercentageDiscount(20));
echo $cart->checkout(100);

Đầu ra:

100
80

Mẫu chiến lược (Strategy Pattern) phát huy hiệu quả khi bạn có nhiều cách thực hiện một thao tác và muốn chuyển đổi giữa chúng mà không cần sửa lớp đang sử dụng chúng. Thêm một loại giảm giá mới nghĩa là tạo một lớp mới, chứ không phải chỉnh sửa code hiện có.

challenge icon

Thử thách

Dễ

Hãy xây dựng một bộ tính chi phí vận chuyển bằng Mẫu chiến lược (Strategy Pattern). Mỗi phương thức vận chuyển có quy tắc tính giá riêng: vận chuyển tiêu chuẩn tính phí cố định, vận chuyển nhanh cộng thêm một tỷ lệ phần trăm, còn vận chuyển qua đêm kết hợp cả hai. Mẫu chiến lược cho phép bạn thay đổi các phép tính này mà không cần sửa code sử dụng chúng.

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

  • ShippingStrategy.php: Tạo một giao diện ShippingStrategy xác định hợp đồng cho mọi phương thức vận chuyển. Giao diện này cần có phương thức calculate(float $weight): float, nhận trọng lượng gói hàng và trả về chi phí vận chuyển.
  • ShippingMethods.php: Đưa tệp ShippingStrategy vào và tạo ba lớp triển khai giao diện:
    • StandardShipping: Tính phí cố định $5.00 bất kể trọng lượng
    • ExpressShipping: Tính phí $2.00 mỗi ki-lô-gam (nhân trọng lượng với 2)
    • OvernightShipping: Tính phí cơ bản $10.00 cộng thêm $3.00 mỗi ki-lô-gam
  • ShippingCalculator.php: Đưa tệp ShippingStrategy vào và tạo lớp ShippingCalculator đóng vai trò ngữ cảnh. Bộ tính của bạn cần:
    • Nhận một ShippingStrategy trong hàm khởi tạo (dùng cú pháp khai báo thuộc tính trong hàm khởi tạo)
    • Có phương thức setStrategy(ShippingStrategy $strategy): void để thay đổi phương thức vận chuyển khi chương trình đang chạy
    • Có phương thức calculateCost(float $weight): float chuyển việc tính toán cho chiến lược hiện tại
  • main.php: Đưa các tệp ShippingMethods và ShippingCalculator vào. Bạn sẽ nhận hai đầu vào: loại hình vận chuyển ("standard", "express" hoặc "overnight") và trọng lượng gói hàng.

    Tạo một ShippingCalculator bắt đầu với StandardShipping. Dựa trên đầu vào về loại hình vận chuyển, dùng setStrategy() để chuyển sang phương thức vận chuyển phù hợp. Sau đó tính chi phí theo trọng lượng đã cho và in kết quả với hai chữ số thập phân.

    Đầu ra cần là: Shipping cost: $[cost]

Thử thách này cho thấy Mẫu chiến lược giúp hệ thống vận chuyển linh hoạt như thế nào: để thêm một phương thức vận chuyển mới như SameDayShipping, bạn chỉ cần tạo một lớp mới triển khai giao diện mà không cần sửa bộ tính hoặc các phương thức vận chuyển hiện có.

Tự làm thử

<?php
require_once 'ShippingMethods.php';
require_once 'ShippingCalculator.php';

// Đọc đầu vào
$shippingType = trim(fgets(STDIN));
$weight = floatval(fgets(STDIN));

// TODO: Tạo ShippingCalculator với StandardShipping ban đầu

// TODO: Dựa vào loại vận chuyển đầu vào ("standard", "express" hoặc "overnight"),
// dùng setStrategy() để chuyển sang phương thức vận chuyển phù hợp

// TODO: Tính chi phí cho trọng lượng đã cho

// TODO: In kết quả với hai chữ số thập phân
// Định dạng đầu ra: Shipping cost: $[cost]

?>
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 PHP online