Định nghĩa
Strategy Pattern là một trong những behavioral design pattern. Nó cho phép bạn định nghĩa một họ các thuật toán, đóng gói từng thuật toán, và làm cho chúng có thể hoán đổi cho nhau. Mục tiêu là để thay đổi hành vi của một class tại runtime mà không cần thay đổi mã nguồn của nó.
Mục đích:
- Tách rời một thuật toán cụ thể khỏi logic xử lý chính.
- Cho phép hoán đổi các thuật toán tại runtime.
Ví dụ: Tính chiết khấu dựa trên loại khách hàng
// Strategy interface
interface DiscountStrategy { double applyDiscount(double amount);
} // Concrete Strategies
class RegularCustomerDiscount implements DiscountStrategy { public double applyDiscount(double amount) { return amount * 0.95; // 5% off }
} class PremiumCustomerDiscount implements DiscountStrategy { public double applyDiscount(double amount) { return amount * 0.90; // 10% off }
} // Context
class BillingService { private DiscountStrategy discountStrategy; public BillingService(DiscountStrategy discountStrategy) { this.discountStrategy = discountStrategy; } public void setDiscountStrategy(DiscountStrategy discountStrategy) { this.discountStrategy = discountStrategy; } public double calculateTotal(double amount) { return discountStrategy.applyDiscount(amount); }
}
Sử dụng
// Main usage
public class StrategyPatternDemo { public static void main(String[] args) { BillingService billing = new BillingService(new RegularCustomerDiscount()); System.out.println("Total: " + billing.calculateTotal(100)); billing.setDiscountStrategy(new PremiumCustomerDiscount()); System.out.println("Total: " + billing.calculateTotal(100)); }
}
Tóm lại:
- Strategy tập trung vào việc hoán đổi cách thực hiện một logic cụ thể.
- Thường dùng khi có nhiều thuật toán thay thế nhau.
Nhìn nó cứ na ná Command Pattern đúng không ) Thề là lúc mình tìm hiểu thì khó phân biệt thật. Giờ mình sẽ thêm 1 ví dụ lại về Command sẽ thấy rõ sự khác biệt và mục đích sử dụng của 2 thằng này rõ ràng hơn. Thật ra thì Pattern cũng chỉ sinh ra để thuận tiện cho việc giải quyết vấn đề chứ chẳng có gì to tác cả nên cũng không hẳn phải phân biệt 2 thằng này như nào cho đau đầu. Cứ nhớ cách triển khai nếu gặp phải vấn đề và tận dụng nó là được.
Command Pattern – chuyển hành động thành đối tượng
Mục đích:
- Biến một hành động (ví dụ: save, delete, print) thành một đối tượng độc lập.
- Cho phép trì hoãn, xếp hàng, ghi lại lịch sử, hoàn tác (undo), hoặc gửi đi nơi khác thực thi.
Ví dụ: Lưu thao tác (Undo/Redo) trong trình soạn thảo văn bản
// Command interface
interface Command { void execute();
} // Receiver
class Document { void save() { System.out.println("Document saved"); } void print() { System.out.println("Document printed"); }
} // Concrete Commands
class SaveCommand implements Command { private Document document; public SaveCommand(Document document) { this.document = document; } public void execute() { document.save(); }
} class PrintCommand implements Command { private Document document; public PrintCommand(Document document) { this.document = document; } public void execute() { document.print(); }
} // Invoker
class CommandInvoker { private List<Command> history = new ArrayList<>(); public void executeCommand(Command command) { command.execute(); history.add(command); }
} // Main usage
public class CommandPatternDemo { public static void main(String[] args) { Document doc = new Document(); Command save = new SaveCommand(doc); Command print = new PrintCommand(doc); CommandInvoker invoker = new CommandInvoker(); invoker.executeCommand(save); invoker.executeCommand(print); }
}
Tóm lại:
- Command biến từng hành động thành một đối tượng có thể truyền đi, lưu trữ hoặc hoàn tác.
- Rất phù hợp cho Undo/Redo, Queue, hoặc Remote execution.
So sánh nhanh:
Tiêu chí | Command | Strategy |
---|---|---|
Mục đích | Biến một hành động thành đối tượng độc lập để trì hoãn, lưu lịch sử, undo, gửi đi nơi khác | Thay thế các thuật toán khác nhau tại runtime |
Trọng tâm | Hành động | Thuật toán |
Có lưu trạng thái không? | Có thể (thường có) | Không cần thiết |
Dùng trong Undo/Redo? | Có | Không |
Dùng để hoán đổi cách xử lý logic? | Không | Có |
Áp dụng Strategy Pattern trong Spring Boot
Tình huống thực tế
Bạn có thể sử dụng Strategy Pattern khi có nhiều cách xử lý logic tùy theo loại dữ liệu đầu vào
- Tính phí theo loại tài sản cầm cố
- Gửi thông báo qua nhiều kênh (email, SMS, push)
- Tính toán lãi suất theo loại sản phẩm vay
Ví dụ thực tế với Spring Boot
Tình huống: Gửi thông báo cho khách hàng bằng các phương thức khác nhau.
Step 1: Interface
public interface NotificationStrategy { void send(String message, String receiver);
}
Step 2: Các implement
@Component("emailNotification")
public class EmailNotificationStrategy implements NotificationStrategy { public void send(String message, String receiver) { System.out.println("Send EMAIL to " + receiver + ": " + message); }
} @Component("smsNotification")
public class SmsNotificationStrategy implements NotificationStrategy { public void send(String message, String receiver) { System.out.println("Send SMS to " + receiver + ": " + message); }
}
Step 3: Context sử dụng strategy
@Service
public class NotificationService { private final Map<String, NotificationStrategy> strategies; // Spring sẽ tự inject tất cả các bean implement interface này public NotificationService(List<NotificationStrategy> strategyList) { this.strategies = strategyList.stream() .collect(Collectors.toMap(s -> s.getClass().getAnnotation(Component.class).value(), Function.identity())); } public void sendNotification(String type, String message, String receiver) { NotificationStrategy strategy = strategies.get(type); if (strategy != null) { strategy.send(message, receiver); } else { throw new IllegalArgumentException("No such strategy: " + type); } }
}
Step 4: Controller
@RestController
@RequestMapping("/notify")
public class NotificationController { @Autowired private NotificationService service; @PostMapping public ResponseEntity<Void> notify(@RequestParam String type, @RequestParam String message, @RequestParam String to) { service.sendNotification(type, message, to); return ResponseEntity.ok().build(); }
}