본문 바로가기
Design Pattern/행동 패턴(Behavioral Patterns)

상태(State) - 행동 패턴(Behavioral Patterns)

by 김 민 준 2024. 5. 25.

 

상태 패턴은 개체가 내부 상태에 따라 행동을 변경하도록 하는 디자인 패턴이다. 개체의 상태를 개체로 캡슐화하여 상태 전환을 관리할 수 있다. 상태에 따라 다른 행동을 수행해야하는 경우에 유용한 패턴이다.

 

주요 개념부터 살펴보자.

 

1. Context(문맥) : 상태를 가지며, 현재 상태에 따라 행동을 수행한다. 상태 개체를 관리하고 상태 전환을 처리한다.

2. State (상태) : 상태에 따른 행동을 정의하는 인터페이스이다.

3. ConcreteState (구체적인 상태) : State 인터페이스를 구현하며, 상태에 따른 구체적인 행동을 정의한다.

 

예제 소스코드를 살펴보자. 

 

문서(Document)에 대한 상태 관리이다. 초안 상태, 검토 중 상태, 승인 상태로 전환되어야 하고 각 상태에 따라 다른 행동을 구현해야한다.

 

// State 인터페이스: 상태에 따른 행동을 정의
interface State {
    void draft(Document document);
    void review(Document document);
    void approve(Document document);
}

 

// DraftState 클래스: 초안 상태를 나타내며, 상태 전환을 정의
class DraftState implements State {
    @Override
    public void draft(Document document) {
        System.out.println("Document is already in draft state.");
    }

    @Override
    public void review(Document document) {
        System.out.println("Reviewing the document...");
        document.setState(new ReviewState());
    }

    @Override
    public void approve(Document document) {
        System.out.println("Cannot approve the document. It is in draft state.");
    }
}

// ReviewState 클래스: 검토 중 상태를 나타내며, 상태 전환을 정의
class ReviewState implements State {
    @Override
    public void draft(Document document) {
        System.out.println("Cannot draft the document. It is in review state.");
    }

    @Override
    public void review(Document document) {
        System.out.println("Document is already in review state.");
    }

    @Override
    public void approve(Document document) {
        System.out.println("Approving the document...");
        document.setState(new ApprovedState());
    }
}

// ApprovedState 클래스: 승인 상태를 나타내며, 상태 전환을 정의
class ApprovedState implements State {
    @Override
    public void draft(Document document) {
        System.out.println("Cannot draft the document. It is already approved.");
    }

    @Override
    public void review(Document document) {
        System.out.println("Cannot review the document. It is already approved.");
    }

    @Override
    public void approve(Document document) {
        System.out.println("Document is already approved.");
    }
}

 

 

// Document 클래스: 상태를 관리하고 상태에 따른 행동을 수행
class Document {
    private State state;

    public Document() {
        state = new DraftState();
    }

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

    public void draft() {
        state.draft(this);
    }

    public void review() {
        state.review(this);
    }

    public void approve() {
        state.approve(this);
    }
}

 

// Client 클래스: Document 객체를 생성하고 상태에 따른 행동을 수행
public class StatePatternDemo {
    public static void main(String[] args) {
        Document document = new Document();

        document.draft();   // 문서가 이미 초안 상태임
        document.review();  // 문서를 검토 상태로 전환
        document.approve(); // 문서를 승인 상태로 전환

        document.draft();   // 승인된 문서를 다시 초안 상태로 전환할 수 없음
    }
}

 

//출력

//Document is already in draft state.
//Reviewing the document...
//Approving the document...
//Cannot draft the document. It is already approved.

 

Document 클래스가 Context역할을 한다. State 인터페이스를 구현하며, 상태에 따른 구체적인 행동을 정의하는데 DraftState, ReviewState, ApprovedStat 클래스가 구체적인 상태를 정의한 것이다.

 

실무에서도 다양하게 사용되는데 상태라는게 가장 많은 시스템은 아무래도 승인시스템이 많을것이다.

위의 예시도 비슷하지만 개발자는 대부분의 시스템을 만들때 승인프로세스를 구현하는 경우가 아주 많다. 이때 이런 패턴을 통해서 구현하는 예시 소스코드를 살펴보자.

 

interface OrderState {
    void next(Order order);
    void prev(Order order);
    void printStatus();
}

class NewOrderState implements OrderState {
    @Override
    public void next(Order order) {
        order.setState(new PaidOrderState());
    }

    @Override
    public void prev(Order order) {
        System.out.println("The order is in its root state.");
    }

    @Override
    public void printStatus() {
        System.out.println("Order placed, not paid yet.");
    }
}

class PaidOrderState implements OrderState {
    @Override
    public void next(Order order) {
        order.setState(new ShippedOrderState());
    }

    @Override
    public void prev(Order order) {
        order.setState(new NewOrderState());
    }

    @Override
    public void printStatus() {
        System.out.println("Order paid, not shipped yet.");
    }
}

class ShippedOrderState implements OrderState {
    @Override
    public void next(Order order) {
        order.setState(new DeliveredOrderState());
    }

    @Override
    public void prev(Order order) {
        order.setState(new PaidOrderState());
    }

    @Override
    public void printStatus() {
        System.out.println("Order shipped, not delivered yet.");
    }
}

class DeliveredOrderState implements OrderState {
    @Override
    public void next(Order order) {
        System.out.println("The order is already delivered.");
    }

    @Override
    public void prev(Order order) {
        order.setState(new ShippedOrderState());
    }

    @Override
    public void printStatus() {
        System.out.println("Order delivered to customer.");
    }
}

class Order {
    private OrderState state = new NewOrderState();

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

    public void nextState() {
        state.next(this);
    }

    public void previousState() {
        state.prev(this);
    }

    public void printStatus() {
        state.printStatus();
    }
}

public class OrderProcessingDemo {
    public static void main(String[] args) {
        Order order = new Order();
        
        order.printStatus();
        order.nextState();
        
        order.printStatus();
        order.nextState();
        
        order.printStatus();
        order.nextState();
        
        order.printStatus();
        order.previousState();
        
        order.printStatus();
    }
}

 

 

소스코드는 주문 시스템을 간단하게 구현했다. 상태에 따라 다음단계로 넘어가는데 이런식의 상태 전환에 사용되는 경우가 많다. 주문 시스템이나 승인 프로세스나 가장 중요한건 상태에 따라 캡슐화하고 상태전환을 명확하게 관리할수 있어야 하는것이다.