99 Ngày Java — Ngày 94: Strategy và Observer
Hai mẫu hành vi dùng nhiều nhất trong Java đều xoay quanh một ý: tách thứ thay đổi ra khỏi thứ ổn định. Strategy tách thuật toán khỏi nơi dùng nó. Observer tách nơi phát sinh sự kiện khỏi những nơi phản ứng với sự kiện. Từ Java 8, lambda làm cả hai mẫu gọn hơn rất nhiều.

1. Strategy thay chuỗi if-else
Khi một phương thức rẽ nhánh theo loại để chọn cách tính, mỗi loại mới là một lần sửa phương thức đó và test lại tất cả. Strategy đưa mỗi cách tính vào một lớp cài đặt cùng interface. Nơi dùng chỉ giữ một tham chiếu tới interface và gọi nó. Việc chọn strategy nào thường nằm trong một Map tra theo khóa.
interface DiscountPolicy { Money apply(Money total); }
final class Checkout {
private final Map<CustomerTier, DiscountPolicy> policies;
Checkout(Map<CustomerTier, DiscountPolicy> policies) { this.policies = policies; }
Money totalFor(Order order) {
var policy = policies.getOrDefault(order.tier(), total -> total);
return policy.apply(order.subtotal());
}
}2. Lambda là strategy
Một strategy chỉ có một phương thức chính là functional interface, nên lambda hoặc method reference thay được cả lớp cài đặt. Bạn đã dùng mẫu này mỗi ngày: Comparator truyền vào sort là một strategy so sánh. Lớp riêng vẫn đáng viết khi strategy có trạng thái, có phụ thuộc cần inject hoặc đủ phức tạp để cần test riêng.
Map<CustomerTier, DiscountPolicy> policies = Map.of(
CustomerTier.GOLD, total -> total.multiply(0.90),
CustomerTier.SILVER, total -> total.multiply(0.95));
orders.sort(Comparator.comparing(Order::createdAt).reversed()); // a strategy too3. Observer: phát sự kiện, không gọi trực tiếp
Sau khi thanh toán xong cần gửi email, cộng điểm và ghi nhật ký. Nếu dịch vụ thanh toán gọi thẳng ba nơi đó, nó phải biết cả ba và mỗi việc mới lại sửa nó. Với Observer, nó chỉ phát một sự kiện; các listener tự đăng ký và tự phản ứng. Danh sách listener nên là CopyOnWriteArrayList để việc đăng ký trong lúc đang phát sự kiện không gây ConcurrentModificationException.
record OrderPaid(String orderId, Money amount) {}
final class OrderEvents {
private final List<Consumer<OrderPaid>> listeners = new CopyOnWriteArrayList<>();
Runnable subscribe(Consumer<OrderPaid> listener) {
listeners.add(listener);
return () -> listeners.remove(listener); // call to unsubscribe
}
void publish(OrderPaid event) { listeners.forEach(l -> l.accept(event)); }
}
events.subscribe(event -> mailer.sendReceipt(event.orderId()));4. Những điều cần cẩn thận
Listener được gọi đồng bộ theo mặc định: một listener chậm làm chậm bên phát, và một listener ném ngoại lệ có thể chặn các listener sau nếu bạn không bắt lỗi. Listener không hủy đăng ký sẽ bị bên phát giữ mãi, đúng kiểu memory leak đã nói ở ngày 80. Luồng xử lý cũng khó lần theo hơn vì không còn lời gọi trực tiếp nào để nhảy tới.
Sự kiện là record bất biến, đặt tên ở thì quá khứ: OrderPaid.
Bắt lỗi từng listener để một lỗi không chặn phần còn lại.
Việc bắt buộc phải thành công cùng giao dịch thì gọi trực tiếp, đừng giấu sau sự kiện.
java.util.Observable đã bị deprecated từ Java 9; tự viết hoặc dùng sự kiện của framework.
Strategy cho phép đổi cách làm, Observer cho phép thêm người phản ứng, cả hai mà không sửa code cũ. Ngày 95 kết thúc loạt pattern với hai mẫu cấu trúc: Decorator và Adapter.
Tài liệu đối chiếu
https://refactoring.guru/design-patterns/strategy
https://refactoring.guru/design-patterns/observer
Site Admin
Engineer and writer. Building things with TypeScript and distributed systems.
Bình luận (0)
Bạn cần đăng nhập bằng Google để bình luận.
Hãy là người bình luận đầu tiên.


