Menu
Coddy logo textTech

상태 패턴

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

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

자판기를 생각해 보세요. 자판기는 돈을 기다리고 있는지, 결제를 받았는지, 또는 상품을 내놓고 있는지에 따라 다르게 작동합니다.

각 상태에는 허용되는 작업에 대한 고유한 규칙이 있습니다. 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";
    }
}

context class는 current 상태에 대한 참조를 보유하고 호출을 해당 상태에 delegates합니다:

<?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

상태 패턴은 복잡하게 늘어난 switch 문을 제거하고 새로운 상태를 간단하게 추가할 수 있도록 합니다. 각 상태 클래스는 다음에 어떤 상태가 올지 파악하는 역할을 맡아 로직을 체계적이고 유지 관리하기 쉽게 만듭니다.

challenge icon

챌린지

쉬움

State Pattern을 사용하여 문서 workflow 시스템을 만들어 봅시다. 콘텐츠 관리 시스템의 문서는 draft, review, published라는 서로 다른 단계를 거치며, 각 단계에는 어떤 작업이 허용되는지와 문서를 다음 단계로 진행하려고 할 때 어떤 일이 발생하는지에 대한 고유한 규칙이 있습니다.

코드를 네 개의 파일로 구성합니다:

  • DocumentState.php: 모든 문서 상태에 대한 계약을 정의하는 DocumentState interface를 Define합니다. 두 개의 methods가 있어야 합니다. 문서를 다음 단계로 진행하려고 시도하는 publish(Document $document): string과 현재 상태 이름을 반환하는 getStatus(): string입니다.
  • States.php: DocumentState interface를 포함하고 이를 Implement하는 세 개의 상태 class를 Create합니다:
    • DraftState: publish()가 호출되면 문서를 ReviewState로 전환하고 "Document sent for review"을 반환합니다. 상태는 "Draft"입니다.
    • ReviewState: publish()가 호출되면 문서를 PublishedState로 전환하고 "Document published"를 반환합니다. 상태는 "Under Review"입니다.
    • PublishedState: publish()가 호출되면 상태를 변경하지 않고 "Document is already published"를 반환합니다. 상태는 "Published"입니다.
  • Document.php: States file을 포함하고 context 역할을 하는 Document class를 Create합니다. Document는 다음과 같아야 합니다:
    • constructor에서 title을 accept하고 DraftState에서 시작합니다
    • current state를 changing하기 위한 setState(DocumentState $state): void method를 가집니다
    • current state's publish() method에 delegates하는 publish(): string method를 가집니다
    • current state's status를 반환하는 getStatus(): string method를 가집니다
  • main.php: Document file을 포함합니다. Document에서 publish()를 call할 number를 하나의 input으로 받습니다.

    title이 "My Article"Document를 Create합니다. 먼저 initial status를 Print합니다. 그런 다음 지정된 number만큼 publish()를 call하고, 각 call의 result를 새 line에 printing합니다. 마지막으로 final status를 Print합니다.

    Output format:

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

각 state class는 workflow에서 다음에 올 state를 알고 있으므로 transition logic을 체계적으로 유지하고 쉽게 modify할 수 있습니다. "Archived"와 같은 새 state를 추가하려면 새 class를 Create하고 관련 transitions를 updating하기만 하면 됩니다.

직접 해보기

<?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실력 점검

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

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

직접 연습해 보세요: 온라인 PHP 컴파일러