Hướng dẫn Java Design Pattern – State

Bài viết được sự cho phép của tác giả Nguyễn Hữu Khanh

Trong các ứng dụng, một số đối tượng có thông tin về trạng thái. Hành vi của đối tượng phụ thuộc vào trạng thái của nó tại thời điểm thực thi (run-time) và các phương thức xử lý nghiệp vụ có thể thay đổi trạng thái buộc đối tượng có hành vi xử lý khác đi. Trong trường hợp như vậy, chúng ta có thể sử dụng State Pattern.

State Pattern là gì?

Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.

State Pattern là một trong những Pattern thuộc nhóm hành vi (Behavior Pattern). Nó cho phép một đối tượng thay đổi hành vi của nó khi trạng thái nội bộ của nó thay đổi. Đối tượng sẽ xuất hiện để thay đổi lớp của nó.

  Thông não Java Design Pattern – Dependency Injection

  Tìm hiểu Java Design Pattern – Service Locator

Cài đặt State Pattern như thế nào?

Các thành phần tham gia State Pattern

Các thành phần tham gia State Pattern:

  • Context : được sử dụng bởi Client. Client không truy cập trực tiếp đến State của đối tượng. Lớp Context này chứa thông tin của ConcreteState object, cho hành vi nào tương ứng với trạng thái nào hiện đang được thực hiện.
  • State : là một interface hoặc abstract class xác định các đặc tính cơ bản của tất cả các đối tượng ConcreteState. Chúng sẽ được sử dụng bởi đối tượng Context để truy cập chức năng có thể thay đổi.
  • ConcreteState : cài đặt các phương thức của State. Mỗi ConcreteState có thể thực hiện logic và hành vi của riêng nó tùy thuộc vào Context.

Một vài điểm cần ghi nhớ khi áp dụng pattern này:

  • Một đối tượng nên thay đổi hành vi của nó khi trạng thái bên trong của nó thay đổi.
  • Mỗi State nên được xác định độc lập.
  • Thêm các trạng thái mới sẽ không làm ảnh hưởng đến các trạng thái hoặc chức năng khác.

Tham khảo việc làm Java hấp dẫn trên TopDev

Ví dụ State Pattern

Giả sử chúng ta cần xây dựng một ứng dụng quản lý Document. Một Document có thể bao gồm các trạng thái: tạo mới (New), trình phê duyệt (Submitted), phê duyệt (Approved) và từ chối (Rejected).

Với yêu cầu trên, chương trình của chúng ta như sau:

package com.gpcoder.patterns.behavioral.state.document.bad;

enum DocumentState {
NEW, SUBMITTED, APPROVED, REJECTED
}

class DocumentService {
    private DocumentState state;

    public void setState(DocumentState state) {
        this.state = state;
    }

public void handleRequest() {
    switch (state) {
    case NEW:
        System.out.println("Create a new document");
        break;
    case SUBMITTED:
        System.out.println("Submitted");
        break;
    case APPROVED:
        System.out.println("Approved");
        break;
    case REJECTED:
        System.out.println("Rejected");
        break;

    default:
        break;
    }
  }
}

public class DocumentApp {

    public static void main(String[] args) {
        DocumentService service = new DocumentService();

        service.setState(DocumentState.NEW);
        service.handleRequest();

        service.setState(DocumentState.SUBMITTED);
        service.handleRequest();

        service.setState(DocumentState.APPROVED);
        service.handleRequest();
    }
}

Output của chương trình:

Create a new document
Submitted
Approved

Như bạn thấy chương trình trên chạy ok, không vấn đề gì. Nhưng bây giờ chúng ta muốn thêm một trạng thái mới như lưu nháp (Save Draft). Đơn giản chúng ta chỉ cần thêm vào enum một giá trị mới và thêm điều kiện xử lý trong switch-case. Tuy nhiên, nếu làm như vậy thì chúng ta đã vi phạm nguyên tắc Open/Close. Mỗi khi có thêm một trạng thái mới chúng ta phải sửa nhiều nơi, code trong phương thức handleRequest() ngày càng nhiều và cần phải test lại toàn bộ app.

Bây giờ chúng ta hãy áp dụng State Pattern cho chương trình trên:

design-patterns-state-example

  • Đầu tiên chúng ta sẽ tạo 1 base inteface để nhận yêu cầu xử lý. Lớp này gọi là State.
  • Tiếp theo, ứng với mỗi giá trị trong enum, chúng ta sẽ tạo một class mới và implement các phương thức của State.
  • Cuối cùng, chúng ta tạo một class Context. Class này chứa thông tin State hiện tại và nhận yêu cầu xử lý trực tiếp từ Client.

State.java

package com.gpcoder.patterns.behavioral.state.document.improve;

public interface State {

    void handleRequest();
}

NewState.java

package com.gpcoder.patterns.behavioral.state.document.improve;

public class NewState implements State {

    @Override
    public void handleRequest() {
        System.out.println("Create a new document");
    }
}

SubmittedState.java

package com.gpcoder.patterns.behavioral.state.document.improve;

public class SubmittedState implements State {

    @Override
    public void handleRequest() {
        System.out.println("Submitted");
    }
}

ApprovedState.java

package com.gpcoder.patterns.behavioral.state.document.improve;

public class ApprovedState implements State {

    @Override
    public void handleRequest() {
        System.out.println("Approved");
    }
}

RejectedState.java

package com.gpcoder.patterns.behavioral.state.document.improve;

public class RejectedState implements State {

    @Override
    public void handleRequest() {
        System.out.println("Rejected");
    }
}

DocumentContext.java

package com.gpcoder.patterns.behavioral.state.document.improve;

public class DocumentContext {

    private State state;

    public void setState(State state) {
        this.state = state;
    }

    public void applyState() {
        this.state.handleRequest();
    }
}

StatePatternExample.java

package com.gpcoder.patterns.behavioral.state.document.improve;

public class StatePatternExample {

    public static void main(String[] args) {
        DocumentContext context = new DocumentContext();

        context.setState(new NewState());
        context.applyState();

        context.setState(new SubmittedState());
        context.applyState();

        context.setState(new ApprovedState());
        context.applyState();
    }
}

Output của chương trình:

Create a new document
Submitted
Approved

Như bạn thấy, kết quả cũng không đổi. Tuy nhiên, chúng ta rất dễ dàng mở rộng. Nếu muốn thêm một trạng thái mới như lưu tạm (Save Draft), đơn giản tạo một class mới implements từ State mà không làm ảnh hưởng đến các State khác và Context.

Lợi ích của State Pattern là gì?

Lợi ích:

  • Đảm bảo nguyên tắc Single responsibility principle (SRP) : tách biệt mỗi State tương ứng với 1 class riêng biệt.
  • Đảm bảo nguyên tắc Open/Closed Principle (OCP) : chúng ta có thể thêm một State mới mà không ảnh hưởng đến State khác hay Context hiện có.
  • Giữ hành vi cụ thể tương ứng với trạng thái.
  • Giúp chuyển trạng thái một cách rõ ràng.

Sử dụng State Pattern khi nào?

  • Khi hành vi của đối tượng phụ thuộc vào trạng thái của nó và nó phải có khả năng thay đổi hành vi của nó lúc run-time theo trạng thái mới.
  • Khi nhiều điều kiện phức tạp buộc đối tượng phụ thuộc vào trạng thái của nó.

Xem thêm Việc làm IT hấp dẫn trên TopDev