Tất cả bài viết

Strategy vs State — hành vi hay trạng thái đang thay đổi?

Strategy và State trông giống nhau về cấu trúc nhưng giải quyết vấn đề khác nhau. Hiểu sai là dùng sai — và code sẽ không express đúng intent.

design-patternsstrategystate

Hai pattern này trông giống nhau đến mức nhiều người dùng nhầm cả đời mà không biết. Cả hai đều có một interface, nhiều implementation, và một object nào đó giữ reference đến implementation đó. Structure diagram gần như y chang nhau.

Sự khác biệt không nằm ở code. Nó nằm ở ý định.


Strategy: hành vi được chọn từ bên ngoài

Strategy giải quyết bài toán: “Cùng một hành động, nhưng cách thực hiện khác nhau tùy theo context.”

Điểm mấu chốt: caller quyết định strategy nào được dùng. Object không tự thay đổi strategy của mình — nó nhận strategy từ ngoài vào, và thực thi theo đó.

Trong HMS, payment là ví dụ điển hình. Một appointment có thể được thanh toán bằng tiền mặt, thẻ ngân hàng, hay bảo hiểm. Logic tính toán và xử lý khác nhau hoàn toàn — nhưng từ góc độ PaymentService, việc cần làm là như nhau: xử lý một payment.

// Strategy interface
public interface PaymentStrategy {
    PaymentResult process(PaymentRequest request);
    boolean supports(PaymentMethod method);
}

// Implementation 1
@Component
public class CashPaymentStrategy implements PaymentStrategy {
    
    @Override
    public PaymentResult process(PaymentRequest request) {
        // Ghi nhận thanh toán tiền mặt, không cần call external API
        CashReceipt receipt = receiptGenerator.generate(request);
        return PaymentResult.success(receipt.getReceiptNumber());
    }
    
    @Override
    public boolean supports(PaymentMethod method) {
        return method == PaymentMethod.CASH;
    }
}

// Implementation 2
@Component
public class InsurancePaymentStrategy implements PaymentStrategy {
    
    @Override
    public PaymentResult process(PaymentRequest request) {
        // Gửi claim lên hệ thống bảo hiểm, chờ approval
        InsuranceClaim claim = insuranceGateway.submitClaim(request);
        return claim.isApproved() 
            ? PaymentResult.success(claim.getClaimId())
            : PaymentResult.pending(claim.getClaimId());
    }
    
    @Override
    public boolean supports(PaymentMethod method) {
        return method == PaymentMethod.INSURANCE;
    }
}

// Service dùng Strategy — không biết và không cần biết implementation cụ thể
@Service
public class PaymentService {
    
    private final List<PaymentStrategy> strategies;
    
    public PaymentResult processPayment(PaymentRequest request) {
        PaymentStrategy strategy = strategies.stream()
            .filter(s -> s.supports(request.getPaymentMethod()))
            .findFirst()
            .orElseThrow(() -> new UnsupportedPaymentMethodException(request.getPaymentMethod()));
        
        return strategy.process(request);
    }
}

Strategy được chọn dựa trên request.getPaymentMethod() — đến từ bên ngoài. PaymentService không tự quyết định, không tự chuyển đổi strategy trong quá trình xử lý.


State: hành vi thay đổi theo trạng thái nội tại

State giải quyết bài toán hoàn toàn khác: “Cùng một object, nhưng hành vi khác nhau tùy theo trạng thái hiện tại của nó.”

Điểm mấu chốt: object tự chuyển đổi state của mình khi có sự kiện xảy ra. Caller không cần biết object đang ở state nào — nó chỉ gọi method, object tự xử lý.

Appointment trong HMS có nhiều trạng thái: PENDING, CONFIRMED, IN_PROGRESS, COMPLETED, CANCELLED. Hành vi của nó với cùng một thao tác — ví dụ cancel() — khác nhau tùy trạng thái:

public interface AppointmentState {
    void confirm(AppointmentContext ctx);
    void cancel(AppointmentContext ctx);
    void complete(AppointmentContext ctx);
    AppointmentStatus getStatus();
}

public class PendingState implements AppointmentState {
    
    @Override
    public void confirm(AppointmentContext ctx) {
        // PENDING -> CONFIRMED: hợp lệ, chuyển state và gửi notification
        ctx.setState(new ConfirmedState());
        ctx.publishEvent(new AppointmentConfirmedEvent(ctx.getAppointmentId()));
    }
    
    @Override
    public void cancel(AppointmentContext ctx) {
        // PENDING -> CANCELLED: hợp lệ, hoàn tiền đặt cọc nếu có
        ctx.setState(new CancelledState());
        ctx.publishEvent(new AppointmentCancelledEvent(ctx.getAppointmentId()));
    }
    
    @Override
    public void complete(AppointmentContext ctx) {
        // Không thể complete khi chưa confirm
        throw new InvalidStateTransitionException("Cannot complete a PENDING appointment");
    }
    
    @Override
    public AppointmentStatus getStatus() { return AppointmentStatus.PENDING; }
}

public class CompletedState implements AppointmentState {
    
    @Override
    public void confirm(AppointmentContext ctx) {
        throw new InvalidStateTransitionException("Appointment already completed");
    }
    
    @Override
    public void cancel(AppointmentContext ctx) {
        // Không thể cancel sau khi đã khám xong
        throw new InvalidStateTransitionException("Cannot cancel a completed appointment");
    }
    
    @Override
    public void complete(AppointmentContext ctx) {
        throw new InvalidStateTransitionException("Appointment already completed");
    }
    
    @Override
    public AppointmentStatus getStatus() { return AppointmentStatus.COMPLETED; }
}

Caller chỉ cần gọi:

appointment.cancel(); // Object tự biết mình đang ở state nào, xử lý đúng theo đó

Không có if-else kiểu if (status == PENDING) { ... } else if (status == CONFIRMED) { ... } nằm trong service. Logic đó được đặt đúng chỗ — trong chính object đang có state đó.


Câu hỏi để phân biệt

Khi mày đang design và không chắc nên dùng cái nào, hỏi:

“Ai quyết định behavior nào được dùng?”

“Behavior có thể thay đổi trong vòng đời của một object không?”


Sai lầm phổ biến

Dùng Strategy để implement State machine — viết một đống if (currentStrategy instanceof XxxStrategy) để kiểm tra trạng thái. Đây là dấu hiệu mày đang dùng sai tool. Strategy không có khái niệm “transition” giữa các implementation. State thì có.

Và ngược lại — dùng State khi hành vi đơn giản chỉ cần được inject từ ngoài. Không phải mọi if-else đều cần trở thành State machine.


Takeaway

Trong HMS, Appointment là ứng viên hoàn hảo cho State pattern vì nó có lifecycle rõ ràng và behavior thay đổi theo từng giai đoạn. PaymentService là ứng viên cho Strategy vì caller biết và chọn payment method. Nhìn lại code mày — mày có đang dùng if-else để kiểm tra trạng thái một object trước khi quyết định làm gì không? Đó là State pattern đang chờ được extract ra.


Bài tiếp theo: Observer — tại sao notification không được gọi trong transaction