Refactoring: Áp dụng State Pattern xử lý trạng thái đơn hàng

· 5 min read

Trong các ứng dụng thương mại điện tử hoặc quản lý quy trình (workflow), trạng thái (State) của một đối tượng thường là nguồn gốc của sự phức tạp.

Ví dụ một Order có thể có các trạng thái: Pending -> Paid -> Processing -> Shipped -> Completed (hoặc Cancelled).

Vấn đề: "Fat Model" và If/Else vô tận

Cách làm thông thường (và sai lầm khi scale) là nhồi nhét logic vào Model hoặc Service:

class Order extends Model 
{
    public function cancel()
    {
        if ($this->status === 'completed') {
            throw new Exception("Không thể hủy đơn đã hoàn thành");
        }
        
        if ($this->status === 'shipped') {
            // Logic hoàn trả kho, gọi API vận chuyển...
        }

        if ($this->status === 'paid') {
            // Logic refund tiền...
        }

        $this->update(['status' => 'cancelled']);
    }
}

Mỗi khi thêm một trạng thái mới, bạn phải đi sửa lại tất cả các phương thức (cancel, ship, pay...) để thêm if/else. Điều này vi phạm nguyên tắc Open/Closed của SOLID.

Giải pháp: State Pattern

State Pattern cho phép một đối tượng thay đổi hành vi khi trạng thái nội tại của nó thay đổi. Chúng ta sẽ tách mỗi trạng thái thành một Class riêng biệt.

1. Định nghĩa Interface

interface OrderStateContract 
{
    public function pay(): void;
    public function ship(): void;
    public function cancel(): void;
}

2. Base Abstract Class (Optional)

Để tránh lặp lại code cho các hành động không được phép (ví dụ: đang Pending thì không thể ship), ta dùng abstract class:

abstract class OrderState implements OrderStateContract
{
    protected Order $order;

    public function __construct(Order $order)
    {
        $this->order = $order;
    }

    public function pay(): void 
    {
        throw new Exception("Trạng thái này không thể thanh toán.");
    }
    
    // ... default implementations throw exceptions
}

3. Implement từng State cụ thể

PendingState:

class PendingState extends OrderState 
{
    public function pay(): void
    {
        // Logic thanh toán
        // ...
        
        // Chuyển trạng thái
        $this->order->transitionTo(new PaidState($this->order));
    }

    public function cancel(): void
    {
        $this->order->transitionTo(new CancelledState($this->order));
    }
}

PaidState:

class PaidState extends OrderState 
{
    public function ship(): void
    {
        // Gọi API giao vận
        // ...
        $this->order->transitionTo(new ShippedState($this->order));
    }

    public function cancel(): void
    {
        // Logic hoàn tiền (Refund) đặc biệt cho trạng thái này
        PaymentGateway::refund($this->order);
        
        $this->order->transitionTo(new CancelledState($this->order));
    }
}

4. Tích hợp vào Model

Model Order giờ đây chỉ đóng vai trò Context:

class Order extends Model
{
    // Giả sử có cột 'state_class' lưu tên class state hiện tại
    // Hoặc map từ enum status sang class
    
    public function state(): OrderStateContract
    {
        $stateClass = $this->state_class ?? PendingState::class;
        return new $stateClass($this);
    }

    public function transitionTo(OrderStateContract $state)
    {
        $this->update(['state_class' => get_class($state)]);
        
        // Có thể bắn event logs transition tại đây
    }
    
    // Delegate actions to state
    public function pay() => $this->state()->pay();
    public function cancel() => $this->state()->cancel();
    public function ship() => $this->state()->ship();
}

Lợi ích

  1. Single Responsibility: Logic của trạng thái Paid chỉ nằm trong class PaidState.
  2. Open/Closed: Muốn thêm trạng thái Refunded? Chỉ cần tạo class mới, không cần sửa logic cũ.
  3. Clean Code: Loại bỏ hoàn toàn các chuỗi if ($status === '...').
  4. Testability: Dễ dàng unit test từng State riêng biệt.

Sử dụng Package?

Nếu không muốn tự implement, laravel-model-status hoặc spatie/laravel-model-states là những lựa chọn tuyệt vời. Đặc biệt gói của Spatie hỗ trợ lưu status vào DB và map ra class tự động rất mượt.

Kết luận

State Pattern là một vũ khí lợi hại khi bạn cảm thấy việc quản lý quy trình nghiệp vụ trở nên rối rắm. Nó biến code phức tạp thành những mảnh ghép nhỏ, dễ hiểu và an toàn hơn rất nhiều.

Bình luận