상태 패턴
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 문을 제거하고 새로운 상태를 추가하는 것을 간단하게 만듭니다. 각 상태 클래스는 다음 상태가 무엇인지 아는 책임을 가지며, 로직을 체계적이고 유지보수 가능하게 유지합니다.
챌린지
쉬움상태 패턴(State Pattern)을 사용하여 문서 워크플로우 시스템을 구축해 보겠습니다. 콘텐츠 관리 시스템의 문서는 초안(draft), 검토(review), 게시(published)와 같은 여러 단계를 거치며, 각 단계마다 허용되는 작업과 문서를 다음 단계로 진행하려고 할 때 발생하는 상황에 대한 고유한 규칙이 있습니다.
코드는 다음 네 개의 파일로 구성됩니다:
DocumentState.php— 모든 문서 상태에 대한 계약을 설정하는DocumentState인터페이스를 정의합니다. 이 인터페이스는 두 가지 메서드를 가져야 합니다: 문서를 다음 단계로 진행시키기 위한publish(Document $document): string와 현재 상태 이름을 반환하기 위한getStatus(): string입니다.States.php— DocumentState 인터페이스를 포함하고 이를 구현하는 세 가지 상태 클래스를 생성합니다: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 파일을 포함하고 컨텍스트 역할을 하는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]" 형식으로 출력
?>이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8매직 메서드
매직 메서드 소개__toString 및 __debugInfo__get, __set, __isset, __unset__call 및 __callStatic__clone 및 객체 복제__serialize 및 __unserialize요약 - 커스텀 컬렉션