Strategy pattern là gì?
Strategy pattern trong Java là một trong những mẫu thiết kếcho hành vi (behavioral). Nó được dùng khi chúng ta có nhiều thuật toán cho cùng một tác vụ cụ thể và client sẽ quyết định thuật toán thực tế nào sẽ được sử dụng tại runtime.
Pattern này còn có tên gọi khác là Policy pattern. Ta sẽ định nghĩa nhiều thuật toán và cho phép ứng dụng client truyền vào thuật toán muốn sử dụng dưới dạng một tham số.
Một trong những ví dụ tiêu biểu nhất của Strategy pattern là Collections.sort()
, một phương thức nhận tham số Comparator
. Tùy thuộc vào các triển khai khác nhau của Comparator
interface, các đối tượng sẽ được sắp xếp theo những cách khác nhau.
Ở ví dụ dưới đây, chúng ta sẽ thử triển khai một giỏ hàng mua sắm trực tuyến đơn giản với hai phương thức thanh toán: thẻ tín dụng hoặc PayPal. Đầu tiên, ta sẽ tạo interface cho nó, trong trường này là để thanh toán số tiền đã được truyền vào như một tham số.
File PaymentStrategy.java
:
package com.journaldev.design.strategy;
public interface PaymentStrategy {
public void pay(int amount);
}
Tiếp theo, chúng ta sẽ tạo các triển khai cụ thể cho những thuật toán thanh toán: sử dụng thẻ tín dụng/ghi nợ hoặc qua PayPal.
File CreditCardStrategy.java
:
package com.journaldev.design.strategy;
public class CreditCardStrategy implements PaymentStrategy {
private String name;
private String cardNumber;
private String cvv;
private String dateOfExpiry;
public CreditCardStrategy(String nm, String ccNum, String cvv, String expiryDate){
this.name=nm;
this.cardNumber=ccNum;
this.cvv=cvv;
this.dateOfExpiry=expiryDate;
}
@Override
public void pay(int amount) {
System.out.println(amount +" paid with credit/debit card");
}
}
File PaypalStrategy.java
:
package com.journaldev.design.strategy;
public 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.");
}
}
Ta đã tạo xong các thuật toán cho ví dụ Strategy pattern của chúng ta. Tiếp theo, ta sẽ bắt đầu triển khai giỏ hàng. Ở đó phương thức thanh toán sẽ yêu cầu một chiến lược thanh toán (Payment strategy) làm input.
File Item.java
:
package com.journaldev.design.strategy;
public class Item {
private String upcCode;
private int price;
public Item(String upc, int cost){
this.upcCode=upc;
this.price=cost;
}
public String getUpcCode() {
return upcCode;
}
public int getPrice() {
return price;
}
}
File ShoppingCart.java
:
package com.journaldev.design.strategy;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
public class ShoppingCart {
//List of items
List<Item> items;
public ShoppingCart(){
this.items=new ArrayList<Item>();
}
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 pay(PaymentStrategy paymentMethod){
int amount = calculateTotal();
paymentMethod.pay(amount);
}
}
Lưu ý rằng phương thức thanh toán của giỏ hàng nhận thuật toán thanh toán làm tham số và không lưu trữ nó ở đâu dưới dạng một biến đối tượng (instance variable).
Tiếp theo ta sẽ kiểm thử thiết lập của Strategy pattern này với một chương trình đơn giản.
File ShoppingCartTest.java
:
package com.journaldev.design.strategy;
public class ShoppingCartTest {
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);
//trả bằng paypal
cart.pay(new PaypalStrategy("myemail@example.com", "mypwd"));
//trả bằng credit card
cart.pay(new CreditCardStrategy("Pankaj Kumar", "1234567890123456", "786", "12/15"));
}
}
Kết quả của chương trình trên là:
50 paid using Paypal.
50 paid with credit/debit card
Sơ đồ các class của Strategy pattern
Những điểm quan trọng của Strategy pattern
- Tuy có thể sử dụng composition (kết hợp các thành phần) để tạo instance variable cho các chiến lược, bạn nên tránh cách này vì ta muốn một chiến lược cụ thể được áp dụng cho một tác vụ nhất định. Nguyên tắc tương tự cũng được áp dụng trong các phương thức
Collections.sort()
vàArrays.sort()
, vốn nhận comparator làm tham số. - Strategy pattern rất tương đồng với State pattern. Một điểm khác biệt là trong State pattern, context chứa state (trạng thái) như một instance variable, và có thể có nhiều tác vụ mà triển khai của chúng phụ thuộc vào state đó. Ngược lại, trong Strategy pattern, strategy được truyền vào phương thức như một tham số, và đối tượng Context không lưu trữ strategy này trong bất kỳ biến nào của nó.
- Strategy pattern có ích khi chúng ta có nhiều thuật toán cho một tác vụ cụ thể và đồng thời muốn ứng dụng đủ linh hoạt để có thể chọn bất kỳ thuật toán cho tác vụ đó nào tại runtime.