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
80Mẫ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ó.
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ệnShippingStrategyxá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ứccalculate(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.00bất kể trọng lượngExpressShipping: Tính phí$2.00mỗi ki-lô-gam (nhân trọng lượng với 2)OvernightShipping: Tính phí cơ bản$10.00cộng thêm$3.00mỗi ki-lô-gam
ShippingCalculator.php: Đưa tệp ShippingStrategy vào và tạo lớpShippingCalculatorđóng vai trò ngữ cảnh. Bộ tính của bạn cần:- Nhận một
ShippingStrategytrong 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): floatchuyển việc tính toán cho chiến lược hiện tại
- Nhận một
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
ShippingCalculatorbắt đầu vớiStandardShipping. Dựa trên đầu vào về loại hình vận chuyển, dùngsetStrategy()để 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]
?>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
1Nền tảng về OOP
Tệp bên ngoàiGiới thiệu về OOPLớp và đối tượngTừ khóa $thisPhương thứcThuộc tínhHàm khởi tạo (__construct)Hàm hủy (__destruct)Ôn tập: máy tính đơn giản4Kế thừa
Kế thừa cơ bảnTừ khóa parent::Ghi đè phương thứcTừ khóa finalLớp trừu tượngÔn tập: hệ thống phân cấp nhân viên7Đóng gói
Công khai, được bảo vệ, riêng tưTìm hiểu sâu về bộ điều chỉnh quyền truy cậpGetter và setterẨn thông tinRút gọn hàm khởi tạo (8.0)Ôn tập: hệ thống hồ sơ học sinh2Không gian tên và tự động nạp
Giới thiệu về không gian tênTừ khóa useChuẩn tự động nạp PSR-4Trình tự động nạp của ComposerÔn tập: dự án có tổ chức5Giao diện và hợp đồng
Giới thiệu về giao diệnTriển khai giao diệnTriển khai nhiều giao diệnGiao diện và lớp trừu tượngGợi ý kiểu với giao diệnÔn tập: Máy tính hình học8Phương thức ma thuật
Giới thiệu về phương thức ma thuật__toString và __debugInfo__get, __set, __isset, __unset__call và __callStatic__clone và sao chép đối tượng__serialize và __unserializeÔn tập: Bộ sưu tập tùy chỉnh3Thuộc tính của lớp
Thuộc tính instance và staticHằng số trong lớpPhương thức và thuộc tính staticThuộc tính private và protectedThuộc tính chỉ đọc (PHP 8.1)Ôn tập: Trình quản lý tài khoản ngân hàng6Đa hình
Xem lại việc ghi đè phương thứcĐa hình qua giao diệnGợi ý kiểu và kiểu hợpLiên kết tĩnh muộnÔn tập: bộ xử lý thanh toánTự luyện tập: Trình biên dịch PHP online