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 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 PayPal

Mẫ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.

challenge icon

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ện IShippingStrategy trong namespace Shipping, với một phương thức CalculateCost(double weight) trả về một double. Sau đó, tạo ba lớp chiến lược cụ thể:
    • StandardShipping - tính chi phí bằng weight * 1.5
    • ExpressShipping - tính chi phí bằng weight * 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ằng weight * 5.0 + 10.0 (dịch vụ cao cấp với phí cơ bản cao hơn)
  • OrderProcessor.cs: Tạo lớp OrderProcessor trong 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 IShippingStrategy hiệ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
  • Program.cs: Kết hợp mọi thứ bằng cách tạo một OrderProcessor và 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, express hoặc overnight
  • 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
    }
}
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