Design pattern/행동 패턴

[디자인 패턴]20장 상태패턴

미스터로즈 2021. 6. 15. 20:17

공부하기 위해서 요약정리해놓은 것입니다..

 

정확하고 꼼꼼한 자료는 쉽게 배워 바로 써먹는 디자인 패턴을 확인하시고

코드는 github.com/infohojin/patterns 을 참고해 주세요.

상태 패턴은 조건에 따른 별개의 동작을 제어문으로 사용하지 않습니다. 그 대신 객체를 캡슐화하여 독립된 동작으로 구분하는 패턴입니다. 상태 패턴은 상태 표현 객체라고 부르기도 합니다.

 

상태란

프로그램은 조건에 따라 분기해 다양한 동작을 처리합니다. 제어문은 조건의 상태 값을 참과 거짓으로 판단하여 상태를 처리합니다.

 

제어문의 조건은 값을 이용해 참과 거짓으로 상태를 구분하고 동작을 제어하는 것입니다.

참 & 거짓 동작으로 나뉘며, 이처럼 2가지 형태로 값의 상태를 구별하는 것을 플래그라고도 합니다.

 

주문 상태를 처리하기 위해 비교할 수 있는 값의 표현을 '상태'라고 합니다. 즉 상태 값은 처리 로직을 구별할 수 있는 특정한 값이라고 생각하면 됩니다.

 

다양한 종류의 상태가 있는 경우 값을 미리 정의합니다. 코드에 직접 리터럴 값을 작성하여 상태를 사용하는 것보다 상수를 정의하여 사용하는 것이 편리합니다.

상수로 상태 값을 정의하면 수정 시 소스코드를 직접 수정하지 않고도 일괄 변경할 수 있습니다.

 

상태 처리

상태 값은 조건 비교를 통해 동작을 분리합니다. 전형적으로 상태를 처리하기 위해 제어문을 사용합니다.

 

<?php

const OPEN = 0x01;      // 주문
const PAY = 0x02;       // 결제중
const ORDERED = 0x04;   // 주문완료

$state = NULL;

$state = OPEN;

if ($state == OPEN) {
    echo "주문\n";
} else if ($state == PAY) {
    echo "결제중\n";
} else if ($state == ORDERED) {
    echo "주문완료\n";
}

if문을 사용하여 상태에 따른 동작을 구별해봅니다. 각 상태에 따라서 else if문을 사용해 체인 형태로 제어문을 연결합니다.

 

새로운 상태가 추가되면 코드를 변경해야 합니다.

<?php

const OPEN = 0x01;      // 주문
const PAY = 0x02;       // 결제중
const ORDERED = 0x04;   // 주문완료
const FINISH = 0x08;    // 처리완료

$state = NULL;
$state = OPEN;

if ($state == OPEN) {
    echo "주문\n";
} else if ($state == PAY) {
    echo "결제중\n";
} else if ($state == ORDERED) {
    echo "주문완료\n";
} else if ($state == FINISH) {
    echo "처리완료\n";
}

이처럼 상태가 추가되면 제어문은 계속 확장되고 코드가 복잡해집니다. 실제 개발 현장에서는 이러한 수정과 변경 작업들이 빈번히 발생하며, 매번 코드를 변경해야 하는 유지 보수 빈도가 늘어납니다.

 

상태를 처리하는 대부분의 로직은 값의 비교입니다, 동일한 값을 비교할 경우 if문 보다 switch문이 더 효율적이며 높은 성능을 발휘합니다.

<?php

const OPEN = 0x01;      // 주문
const PAY = 0x02;       // 결제중
const ORDERED = 0x04;   // 주문완료
const FINISH = 0x08;    // 처리완료

$state = NULL;
$state = ORDERED;

switch ($state) {
    case OPEN:
        echo "주문\n";
        break;
    case PAY:
        echo "결제중\n";
        break;
    case ORDERED:
        echo "주문완료\n";
        break;
    case FINISH:
        echo "처리완료\n";
        break;
}

하지만 코드 가독성과 처리 측면에서 모두 별다른 개선점이 없습니다.

 

가변함수는 변수를 이용해 함수를 호출하는 프로그래밍 문법입니다, 변수명에 함수명을 설정한 후 변수값과 동일한 함수를 호출합니다.

<?php

$state = "ordered";
if($state && function_exists($state)){
    $state();
}

function open()
{
    echo "주문\n";
}

function pay()
{
    echo "결제중\n";
}

function ordered()
{
    echo "주문완료\n";
}

function finish()
{
    echo "처리완료\n";
}

상태값에 따라 처리 로직을 각각의 함수로 분리합니다. 동일한 상태값과 함수명을 사용해 동작을 구분하고 실행합니다. 가변 함수를 이용하여 상태값에 따라 동작을 실행할 수 있습니다.

 

패턴 구현

상태 패턴은 상태값에 따른 동작을 각각의 함수 형태로 구별하는 것과 달리 객체로 동작을 분리합니다.

객체 형태로 상태를 분리할 경우 상태의 동작을 객체에 위임할 수 있습니다. 

<?php

class JinyOrder
{
    const OPEN = 0x01;      // 주문
    const PAY = 0x02;       // 결제중
    const ORDERED = 0x04;   // 주문완료
    const FINISH = 0x08;    // 처리완료

    public function process($state)
    {
        switch ($state) {
            case "OPEN":
                $this->stateOrder();
                break;
            case "PAY":
                $this->statePAY();
                break;
            case "ORDERED":
                $this->stateORDERED();
                break;
            case "FINISH":
                $this->stateFINISH();
                break;
        }
    }

    public function stateOrder()
    {
        echo "주문\n";
    }

    public function statePAY()
    {
        echo "결제중\n";
    }

    public function stateORDERED()
    {
        echo "주문완료\n";
    }

    public function stateFINISH()
    {
        echo "처리완료\n";
    }

}

$obj = new JinyOrder();
$obj->process("FINISH");

클래스로 변경했지만 아직도 복잡한 제어문을 사용하고 있어 별로 나아진 것이 없어 보입니다.

 

상태 패턴으로 변경하기 위해 각 상태의 동작을 캡슐화합니다.

<?php

class StateOrder implements State
{
    public function process()
    {
        echo "주문\n";
    }
}

다른 3개의 함수 역시 동일한 형태로 캡슐화를 진행하면 됩니다.

 

상태 패턴에서는 각각의 상태를 객체로 캡슐화하기 때문에 클래스 파일이 늘어난다는 단점이 있습니다. 그러나 상태 패턴을 사용하지 않고 수많은 조건문을 사용하는 것보다는 유연하게 확장할 수 있습니다.

 

상태 패턴은 서브 클래스 생성 시 인터페이스를 적용하는데, 이 인터페이스는 서브 클래스의 통일성을 유지하기 위해 사용합니다. 인터페이스는 모든 객체에 대응해 동일한 형태의 서브 클래스를 생성하도록 규약을 적용합니다.

따라서 상태 객체는 특정한 상태에 종속된 동작을 가지는 의존성이 발생하게 됩니다.

 

객체 생성

상태 패턴은 상태별로 구분된 객체에 동작을 위임합니다. 위임하기 위해서는 각 상태별 객체의 생성 관리가 필요합니다.

 

각 상태에 따라 동작하는 서브 클래스를 생성합니다. 상태 패턴은 상태마다 동작할 서브 클래스의 객체 정보를 가지고 있습니다.

 

상태에 따른 객체 정보를 변경하여 동작을 위임합니다.