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 55 trên 70.
Mẫu Strategy là một mẫu hành vi cho phép bạn định nghĩa một nhóm thuật toán, đóng gói từng thuật toán trong lớp riêng và hoán đổi chúng cho nhau khi chương trình đang chạy. Thay vì viết cứng hành vi, bạn đưa chiến lược mong muốn vào một đối tượng.
Mẫu này gồm ba phần: một giao diện chiến lược xác định hợp đồng thuật toán, các lớp chiến lược cụ thể triển khai những hành vi khác nhau và một lớp ngữ cảnh sử dụng chiến lược:
public interface IPaymentStrategy
{
void Pay(decimal amount);
}
public class CreditCardPayment : IPaymentStrategy
{
public void Pay(decimal amount) =>
Console.WriteLine($"Paid {amount} via Credit Card");
}
public class PayPalPayment : IPaymentStrategy
{
public void Pay(decimal amount) =>
Console.WriteLine($"Paid {amount} via PayPal");
}
public class ShoppingCart
{
private IPaymentStrategy _paymentStrategy;
public void SetPaymentMethod(IPaymentStrategy strategy)
{
_paymentStrategy = strategy;
}
public void Checkout(decimal total)
{
_paymentStrategy.Pay(total);
}
}Ngữ cảnh (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 cung cấp:
var cart = new ShoppingCart();
cart.SetPaymentMethod(new CreditCardPayment());
cart.Checkout(99.99m); // Đã thanh toán 99.99 bằng thẻ tín dụng
cart.SetPaymentMethod(new PayPalPayment());
cart.Checkout(49.99m); // Đã thanh toán 49.99 qua PayPalMẫu Strategy (Strategy pattern) loại bỏ logic điều kiện phức tạp. Thêm phương thức thanh toán mới nghĩa là tạo một lớp mới implements IPaymentStrategy, không cần thay đổi ShoppingCart. Nhờ vậy, code của bạn có thể mở rộng nhưng không cần chỉnh sửa.
Thử thách
DễHãy xây dựng một trình tính chi phí vận chuyển bằng mẫu Strategy. Các phương thức vận chuyển khác nhau có thuật toán tính giá khác nhau, và hệ thống của bạn cần cho phép chuyển đổi giữa các phương thức này trong khi chương trình đang chạy mà không thay đổi logic xử lý đơn hàng cốt lõi.
Bạn sẽ sắp xếp code trong ba tệp:
ShippingStrategy.cs: Định nghĩa giao diệnIShippingStrategytrong namespaceShipping, với một phương thứcCalculateCost(double weight)trả về mộtdouble. Sau đó, tạo ba lớp chiến lược cụ thể:StandardShipping- tính chi phí bằngweight * 1.5ExpressShipping- tính chi phí bằngweight * 3.0 + 5.0(bao gồm một khoản phí cố định cho dịch vụ chuyển phát nhanh)OvernightShipping- tính chi phí bằngweight * 5.0 + 10.0(dịch vụ cao cấp với phí cơ bản cao hơn)
OrderProcessor.cs: Tạo lớpOrderProcessortrong cùng namespace. Đây là lớp ngữ cảnh sử dụng một chiến lược vận chuyển. Lớp cần có:- Một trường private để lưu
IShippingStrategyhiện tại - Phương thức
SetShippingMethod(IShippingStrategy strategy)để thay đổi chiến lược vận chuyển - Phương thức
ProcessOrder(double weight)sử dụng chiến lược hiện tại để tính và trả về chi phí vận chuyển
- Một trường private để lưu
Program.cs: Kết hợp mọi thứ bằng cách tạo mộtOrderProcessorvà minh họa cách bạn có thể đổi chiến lược vận chuyển khi chương trình đang chạy. Cùng một bộ xử lý có thể tính chi phí bằng các thuật toán khác nhau chỉ bằng cách thay đổi chiến lược.
Bạn sẽ nhận hai đầu vào:
- Loại phương thức vận chuyển:
standard,expresshoặcovernight - Khối lượng gói hàng dưới dạng số thập phân (ví dụ:
4.5)
Tạo một OrderProcessor, đặt chiến lược vận chuyển phù hợp dựa trên loại đầu vào, sau đó xử lý đơn hàng và in ra chi phí đã tính.
Ví dụ: nếu đầu vào là express và 4.5, đầu ra sẽ là:
18.5Điều này cho thấy sức mạnh của mẫu Strategy: OrderProcessor không chứa logic tính chi phí vận chuyển. Nó chỉ ủy quyền việc đó cho chiến lược được cung cấp, nhờ vậy bạn dễ dàng thêm các phương thức vận chuyển mới (như DroneDelivery) mà không cần sửa đổi bộ xử lý!
Tự làm thử
using System;
using Shipping;
class Program
{
public static void Main(string[] args)
{
// Đọc đầu vào
string shippingType = Console.ReadLine();
double weight = Convert.ToDouble(Console.ReadLine());
// TODO: Tạo một instance của OrderProcessor
// TODO: Dựa vào shippingType ("standard", "express" hoặc "overnight"),
// tạo chiến lược vận chuyển phù hợp và gán cho bộ xử lý
// TODO: Xử lý đơn hàng và in ra chi phí đã tính
}
}
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
1Các khái niệm cơ bản về lập trình hướng đối tượng
Tệp bên ngoàiKhông gian tên và chỉ thịGiới thiệu về lớp và đối tượngTừ khóa thisPhương thức và tham sốTrường và thuộc tínhHàm khởi tạoKhởi tạo đối tượngÔn tập: Máy tính đơn giản4Kế thừa
Cú pháp kế thừa cơ bản (:)Từ khóa 'base'Từ khóa virtual và overrideLớp sealedLớp cơ sở 'object'Ôn tập: hệ phân cấp nhân viên7Tính năng nâng cao
Nạp chồng toán tửBộ lập chỉ mục (this[])Ghi đè ToString()Phương thức mở rộngÔn tập: Danh sách tùy chỉnh10Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếSingleton an toàn với luồngMẫu FactoryMẫu Observer (sự kiện)Mẫu Strategy2Thuộc tính và thành viên tĩnh
Thuộc tính tự độngThuộc tính chỉ đọc hoặc chỉ ghiTrường và phương thức tĩnhLớp tĩnhThành viên có thân biểu thức5Đa hình và interface
Đa hình lúc biên dịch và lúc chạyInterface và lớp trừu tượngNhiều interfaceInterface tường minhUpcasting và downcastingÔn tập: bộ tính toán hình học8Các khái niệm OOP nâng cao
Ưu tiên composition hơn kế thừaGenerics (lớp và phương thức)Delegate và sự kiệnAttribute và reflectionGiao diện IDisposable và câu lệnh usingKiến thức cơ bản về dependency injection11Các mẫu thiết kế phần 2
Mẫu lệnhMẫu bộ chuyển đổiMẫu trang tríMẫu phương thức khuôn mẫuMẫu trạng tháiMẫu tổ hợpTự luyện tập: Trình biên dịch C# online