Design-Patterns

Strategy

Khái Niệm

Strategy Pattern, trong lĩnh vực phát triển phần mềm, là một mẫu thiết kế hành vi cho phép định nghĩa một nhóm các thuật toán, đóng gói từng thuật toán lại, và làm cho chúng có thể hoán đổi cho nhau. Strategy cho phép thuật toán biến đổi độc lập với các khách hàng sử dụng nó. Điều này giúp tăng cường tính mô-đun và tái sử dụng của mã, bởi vì nó tách rời việc triển khai của các thuật toán từ các lớp sử dụng chúng.

Tổng quan

Đặt vấn đề

Trong lập trình hướng đối tượng, các ứng dụng thường phải đối mặt với những thách thức liên quan đến việc lựa chọn hành vi thích hợp trong thời gian chạy. Một ví dụ điển hình là việc xử lý các chiến lược thanh toán khác nhau trong một hệ thống thương mại điện tử. Khi không sử dụng Strategy Pattern, việc thêm hoặc thay đổi các phương thức thanh toán có thể yêu cầu sửa đổi lớn trong mã nguồn, dẫn đến việc vi phạm nguyên tắc Mở - Đóng (Open-Closed Principle), làm tăng sự phức tạp và khó khăn trong việc bảo trì.

classDiagram
    class Client {
        +void main()
    }
    class PaymentMethod {
        <<interface>>
        +pay(amount: float)
    }
    class CreditCard {
        +pay(amount: float)
    }
    class Paypal {
        +pay(amount: float)
    }
    Client --> PaymentMethod : uses
    PaymentMethod <|-- CreditCard : implements
    PaymentMethod <|-- Paypal : implements

Giải pháp

Strategy Pattern cung cấp một giải pháp cho vấn đề trên bằng cách định nghĩa một tập hợp các thuật toán, mỗi thuật toán được đóng gói trong một lớp riêng biệt với một interface chung. Điều này cho phép thuật toán có thể thay đổi độc lập với các client sử dụng nó. Trong ví dụ về hệ thống thanh toán, các chiến lược thanh toán khác nhau như Credit Card, PayPal, hoặc Bitcoin có thể được thực hiện như các lớp riêng biệt, giúp việc thêm hoặc sửa đổi các phương thức thanh toán trở nên dễ dàng và linh hoạt hơn.

Việc sử dụng Strategy Pattern giúp tăng cường tính mô đun hóa và tái sử dụng của mã. Nó cũng giúp giảm sự phụ thuộc giữa các lớp và tăng tính linh hoạt cho ứng dụng. Ngoài ra, pattern cũng giúp ứng dụng tuân thủ nguyên tắc Open-Closed, giúp dễ dàng mở rộng mà không cần sửa đổi mã nguồn hiện có.

Mặc dù Strategy Pattern mang lại nhiều lợi ích, nhưng việc sử dụng nó cũng có thể dẫn đến một số sự thỏa hiệp. Ví dụ, nó có thể gây ra sự phức tạp ban đầu khi cần phải thiết kế và triển khai các interface và lớp cụ thể. Ngoài ra, nếu có quá nhiều chiến lược, việc quản lý chúng có thể trở nên khó khăn.

classDiagram
    class PaymentContext {
        -strategy: PaymentStrategy
        +PaymentContext(strategy: PaymentStrategy)
        +executePayment(amount: float): void
    }
    class PaymentStrategy {
        <<interface>>
        +pay(amount: float): void
    }
    class CreditCardPayment {
        +pay(amount: float): void
    }
    class PaypalPayment {
        +pay(amount: float): void
    }
    class BitcoinPayment {
        +pay(amount: float): void
    }
    PaymentContext --> PaymentStrategy : has-a
    PaymentStrategy <|-- CreditCardPayment : implements
    PaymentStrategy <|-- PaypalPayment : implements
    PaymentStrategy <|-- BitcoinPayment : implements

Cấu trúc của Strategy Pattern

classDiagram
      class Context {
        -Strategy strategy
        +Context(Strategy strategy)
        +setStrategy(Strategy strategy)
        +executeStrategy()
      }
      class Strategy {
        <<interface>>
        +algorithmInterface()
      }
      class ConcreteStrategyA {
        +algorithmInterface()
      }
      class ConcreteStrategyB {
        +algorithmInterface()
      }
      class ConcreteStrategyC {
        +algorithmInterface()
      }
      
      Context --> Strategy : has
      Strategy <|.. ConcreteStrategyA : implements
      Strategy <|.. ConcreteStrategyB : implements
      Strategy <|.. ConcreteStrategyC : implements

Mục đích của Strategy Pattern là cho phép thuật toán thay đổi độc lập với các client sử dụng thuật toán đó.

Cách triển khai Strategy Pattern

Để triển khai Strategy Pattern, chúng ta sẽ cần các thành phần sau:

1. Strategy Interface

Đây là interface cho các chiến lược khác nhau trong context. Mỗi chiến lược sẽ cài đặt các hành động cụ thể.

public interface Strategy {
    void executeStrategy();
}

2. Concrete Strategy Classes

Các lớp này cài đặt các hành động cụ thể cho một chiến lược cụ thể.

public class ConcreteStrategyA implements Strategy {
    @Override
    public void executeStrategy() {
        System.out.println("Executing Strategy A");
    }
}

public class ConcreteStrategyB implements Strategy {
    @Override
    public void executeStrategy() {
        System.out.println("Executing Strategy B");
    }
}

3. Context

Lớp này duy trì một tham chiếu đến một đối tượng Strategy và cho phép Client thay đổi strategy.

public class Context {
    private Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public void executeStrategy() {
        strategy.executeStrategy();
    }
}

4. Sử dụng Pattern

Đây là cách chúng ta có thể sử dụng Strategy Pattern trong một ứng dụng.

public class StrategyPatternDemo {
    public static void main(String[] args) {
        Context context = new Context(new ConcreteStrategyA());

        // The context is using ConcreteStrategyA.
        context.executeStrategy(); // Executing Strategy A

        // Change strategy to ConcreteStrategyB
        context.setStrategy(new ConcreteStrategyB());

        // Now the context is using ConcreteStrategyB.
        context.executeStrategy(); // Executing Strategy B
    }
}

Ví dụ

Dưới đây là một ví dụ minh họa cho việc sử dụng Strategy Pattern trong Java, được cấu trúc theo yêu cầu của bạn:

import java.util.ArrayList;
import java.util.List;

// Strategy interface
interface PaymentStrategy {
    void pay(int amount);
}

// Concrete Strategies
class CreditCardStrategy implements PaymentStrategy {
    private String name;
    private String cardNumber;

    public CreditCardStrategy(String nm, String ccNum) {
        this.name = nm;
        this.cardNumber = ccNum;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid with credit/debit card.");
    }
}

class PaypalStrategy implements PaymentStrategy {
    private String emailId;
    private String password;

    public PaypalStrategy(String email, String pwd) {
        this.emailId = email;
        this.password = pwd;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid using Paypal.");
    }
}

// Context class
class ShoppingCart {
    List<Item> items;
    PaymentStrategy paymentStrategy;

    public ShoppingCart() {
        this.items = new ArrayList<>();
    }

    public void addItem(Item item) {
        this.items.add(item);
    }

    public void removeItem(Item item) {
        this.items.remove(item);
    }

    public int calculateTotal() {
        int sum = 0;
        for (Item item : items) {
            sum += item.getPrice();
        }
        return sum;
    }

    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }

    public void checkout() {
        int amount = calculateTotal();
        paymentStrategy.pay(amount);
    }
}

// Item class
class Item {
    private String upcCode;
    private int price;

    public Item(String upc, int cost) {
        this.upcCode = upc;
        this.price = cost;
    }

    public int getPrice() {
        return this.price;
    }
}

// StrategyPatternDemo class
public class StrategyPatternDemo {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();

        Item item1 = new Item("1234", 10);
        Item item2 = new Item("5678", 40);

        cart.addItem(item1);
        cart.addItem(item2);

        // Pay by PayPal
        cart.setPaymentStrategy(new PaypalStrategy("myemail@example.com", "mypwd"));
        cart.checkout();

        // Pay by Credit Card
        cart.setPaymentStrategy(new CreditCardStrategy("John Doe", "1234567890123456"));
        cart.checkout();
    }
}

Trong ví dụ này, ShoppingCart đại diện cho Context class, nó chứa một Strategy (PaymentStrategy), và các Strategies cụ thể (CreditCardStrategy, PaypalStrategy) định nghĩa cách thức thanh toán khác nhau. ShoppingCart có thể thay đổi Strategy của nó tùy ý để sử dụng phương thức thanh toán khác nhau mà không cần sửa đổi mã nguồn của chính nó.

Khi nào nên sử dụng Strategy Pattern