Menu
Coddy logo textTech

상태 패턴

Coddy PHP 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 91개 중 79번째.

상태 패턴(State Pattern)은 객체의 내부 상태가 변경될 때 객체가 행동을 변경할 수 있도록 하는 행동 디자인 패턴입니다. 여러 상태를 처리하기 위해 복잡한 조건문을 사용하는 대신, 각 상태를 별도의 클래스로 캡슐화하고 행동을 현재 상태 객체에 위임합니다.

자판기를 생각해 보세요. 돈을 기다리는 중인지, 결제를 받았는지, 아니면 제품을 배출하는 중인지에 따라 다르게 동작합니다.

각 상태는 허용되는 동작에 대해 고유한 규칙을 가집니다. 상태 패턴(State Pattern)은 각 상태에 대한 클래스를 생성하여 이를 모델링합니다.

<?php
interface OrderState {
    public function proceed(Order $order): string;
    public function getStatus(): string;
}

class PendingState implements OrderState {
    public function proceed(Order $order): string {
        $order->setState(new ProcessingState());
        return "Order moved to processing";
    }
    
    public function getStatus(): string {
        return "Pending";
    }
}

class ProcessingState implements OrderState {
    public function proceed(Order $order): string {
        $order->setState(new ShippedState());
        return "Order has been shipped";
    }
    
    public function getStatus(): string {
        return "Processing";
    }
}

class ShippedState implements OrderState {
    public function proceed(Order $order): string {
        return "Order already shipped";
    }
    
    public function getStatus(): string {
        return "Shipped";
    }
}

컨텍스트 클래스는 현재 상태에 대한 참조를 유지하고 그 상태로 호출을 위임합니다:

<?php
class Order {
    private OrderState $state;
    
    public function __construct() {
        $this->state = new PendingState();
    }
    
    public function setState(OrderState $state): void {
        $this->state = $state;
    }
    
    public function proceed(): string {
        return $this->state->proceed($this);
    }
    
    public function getStatus(): string {
        return $this->state->getStatus();
    }
}

$order = new Order();
echo $order->getStatus() . "\n";
echo $order->proceed() . "\n";
echo $order->getStatus() . "\n";
echo $order->proceed() . "\n";
echo $order->getStatus();

출력:

Pending
Order moved to processing
Processing
Order has been shipped
Shipped

상태 패턴(State Pattern)은 무분별하게 퍼져 있는 switch 문을 제거하고 새로운 상태를 추가하는 것을 간단하게 만듭니다. 각 상태 클래스는 다음 상태가 무엇인지 아는 책임을 가지며, 로직을 체계적이고 유지보수 가능하게 유지합니다.

challenge icon

챌린지

쉬움

상태 패턴(State Pattern)을 사용하여 문서 워크플로우 시스템을 구축해 보겠습니다. 콘텐츠 관리 시스템의 문서는 초안(draft), 검토(review), 게시(published)와 같은 여러 단계를 거치며, 각 단계마다 허용되는 작업과 문서를 다음 단계로 진행하려고 할 때 발생하는 상황에 대한 고유한 규칙이 있습니다.

코드는 다음 네 개의 파일로 구성됩니다:

  • DocumentState.php — 모든 문서 상태에 대한 계약을 설정하는 DocumentState 인터페이스를 정의합니다. 이 인터페이스는 두 가지 메서드를 가져야 합니다: 문서를 다음 단계로 진행시키기 위한 publish(Document $document): string와 현재 상태 이름을 반환하기 위한 getStatus(): string입니다.
  • States.php — DocumentState 인터페이스를 포함하고 이를 구현하는 세 가지 상태 클래스를 생성합니다:
    • DraftStatepublish()가 호출되면 문서를 ReviewState로 전환하고 "Document sent for review"를 반환합니다. 상태는 "Draft"입니다.
    • ReviewStatepublish()가 호출되면 문서를 PublishedState로 전환하고 "Document published"를 반환합니다. 상태는 "Under Review"입니다.
    • PublishedStatepublish()가 호출되면 상태를 변경하지 않고 "Document is already published"를 반환합니다. 상태는 "Published"입니다.
  • Document.php — States 파일을 포함하고 컨텍스트 역할을 하는 Document 클래스를 생성합니다. 이 클래스는 다음을 수행해야 합니다:
    • 생성자에서 제목을 받고 DraftState에서 시작합니다.
    • 현재 상태를 변경하기 위한 setState(DocumentState $state): void 메서드를 가집니다.
    • 현재 상태의 publish() 메서드에 위임하는 publish(): string 메서드를 가집니다.
    • 현재 상태의 상태를 반환하는 getStatus(): string 메서드를 가집니다.
  • main.php — Document 파일을 포함합니다. 입력으로 문서에 대해 publish()를 호출할 횟수를 받습니다.

    제목이 "My Article"Document를 생성합니다. 먼저 초기 상태를 출력합니다. 그런 다음 지정된 횟수만큼 publish()를 호출하고, 각 호출 결과를 새 줄에 출력합니다. 마지막으로 최종 상태를 출력합니다.

    출력 형식:

    Status: [initial status]
    [publish result 1]
    [publish result 2]
    ...
    Status: [final status]

각 상태 클래스는 워크플로우에서 다음에 올 상태를 알고 있으므로 전환 로직을 체계적으로 관리하고 수정하기 쉽게 유지합니다. "Archived"와 같은 새로운 상태를 추가하려면 단순히 새 클래스를 만들고 관련 전환을 업데이트하기만 하면 됩니다.

치트 시트

상태 패턴(State Pattern)은 각 상태를 자체 클래스로 캡슐화하여 객체의 내부 상태가 변경될 때 객체가 동작을 변경할 수 있도록 하는 행동 디자인 패턴입니다.

모든 상태 클래스가 구현할 상태 인터페이스를 정의합니다:

<?php
interface OrderState {
    public function proceed(Order $order): string;
    public function getStatus(): string;
}

인터페이스를 구현하는 구체적인 상태 클래스들을 생성합니다:

<?php
class PendingState implements OrderState {
    public function proceed(Order $order): string {
        $order->setState(new ProcessingState());
        return "Order moved to processing";
    }
    
    public function getStatus(): string {
        return "Pending";
    }
}

class ProcessingState implements OrderState {
    public function proceed(Order $order): string {
        $order->setState(new ShippedState());
        return "Order has been shipped";
    }
    
    public function getStatus(): string {
        return "Processing";
    }
}

class ShippedState implements OrderState {
    public function proceed(Order $order): string {
        return "Order already shipped";
    }
    
    public function getStatus(): string {
        return "Shipped";
    }
}

현재 상태에 대한 참조를 유지하고 호출을 해당 상태로 위임하는 컨텍스트 클래스를 생성합니다:

<?php
class Order {
    private OrderState $state;
    
    public function __construct() {
        $this->state = new PendingState();
    }
    
    public function setState(OrderState $state): void {
        $this->state = $state;
    }
    
    public function proceed(): string {
        return $this->state->proceed($this);
    }
    
    public function getStatus(): string {
        return $this->state->getStatus();
    }
}

사용 예시:

$order = new Order();
echo $order->getStatus() . "\n";  // Pending
echo $order->proceed() . "\n";     // Order moved to processing
echo $order->getStatus() . "\n";   // Processing
echo $order->proceed() . "\n";     // Order has been shipped
echo $order->getStatus();          // Shipped

상태 패턴은 복잡한 switch 문을 제거하고 새로운 상태를 추가하는 것을 간단하게 만듭니다. 각 상태 클래스는 다음 상태가 무엇인지 파악하는 책임을 가집니다.

직접 해보기

<?php

require_once 'Document.php';

// 입력 읽기 - publish()를 호출할 횟수
$n = intval(trim(fgets(STDIN)));

// TODO: 제목이 "My Article"인 Document 생성

// TODO: 초기 상태를 "Status: [status]" 형식으로 출력

// TODO: publish()를 n번 호출하고, 각 결과를 새 줄에 출력

// TODO: 최종 상태를 "Status: [status]" 형식으로 출력

?>
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨