상태 패턴(State Pattern)

상태 패턴이란?

상태에 따라 변경을 쉽게 이뤄주는 패턴이다. 즉, 상태에 대한 알고리즘을 인터페이스를 통해 독자적으로 관리해주며 이들이 필요할 때 교체할 수 있도록 함으로써 모든 상태를 조절해주는 패턴이다.

상황 가정

전투기는 대기, 이륙, 순항, 착률의 상황에 따라 상태가 달라진다.

예시

  • 대기 상태일 때

    → 전투기 바퀴 상태 : 열려 있음

    → 엔진 상태 : 꺼져 있음

    → 통신 장비 상태 : 꺼져 있음

    → 날개 상태 : 접혀 있음

  • 순항 상태 일 때

    → 전투기 바퀴 상태 : 닫혀 있음

    → 엔진 상태 : 켜져 있음

    → 통신 장비 상태 : 켜져 있음

    → 날개 상태 : 펴져 있음

위 상황을 ‘비효율적인 코드’로 구현

public class Airplane {
    // 바퀴 상태 변경
    public String wheelStatusChange(String statusGubun) {
        if (statusGubun.equals("대기")) {
            wheelImplement = "바퀴 열려 있는 상태"; // 실제로는 복잡한 로직
        } else if (statusGubun.equasl("이륙")) {
            wheelImplement = "바퀴 열려 있는 상태";
        } else if (statusGubun.equasl("순항")) {
            wheelImplement = "바퀴 닫혀 있는 상태";
        } else if (statusGubun.equasl("착륙")) {
            wheelImplement = "바퀴 열려 있는 상태";
        }
        return wheelImplement;
    }

    // 엔진 상태 변경
    public String engineStatusChange(String statusGubun) {
        if (statusGubun.equals("대기")) {
            engingImplement = "엔진 꺼져 있는 상태"; // 실제로는 복잡한 로직
        } else if (statusGubun.equasl("이륙")) {
            engingImplement = "엔진 켜 있는 상태";
        } else if (statusGubun.equasl("순항")) {
            engingImplement = "엔진 켜 있는 상태";
        } else if (statusGubun.equasl("착륙")) {
            engingImplement = "엔진 켜 있는 상태";
        }
        return engingImplement;
    }

    // 통신 장비 상태 변경
    public String electronicStatusChange(String statusGubun) {
        if (statusGubun.equals("대기")) {
            electronicImplement = "통신 장비 꺼져 있는 상태"; // 실제로는 복잡한 로직
        } else if (statusGubun.equasl("이륙")) {
            electronicImplement = "통신 장비 켜 있는 상태";
        } else if (statusGubun.equasl("순항")) {
            electronicImplement = "통신 장비 켜 있는 상태";
        } else if (statusGubun.equasl("착륙")) {
            electronicImplement = "통신 장비 켜 있는 상태";
        }
        return electronicImplement;
    }

    // 날개 상태 변경
    public String wingStatusChange(String statusGubun) {
            if (statusGubun.equals("대기")) {
                wingImplement = "날개 접혀 있는 상태"; // 실제로는 복잡한 로직
            } else if (statusGubun.equasl("이륙")) {
                wingImplement = "날개 펴 있는 상태";
            } else if (statusGubun.equasl("순항")) {
                wingImplement = "날개 펴 있는 상태";
            } else if (statusGubun.equasl("착륙")) {
                wingImplement = "날개 펴 있는 상태";
            }
            return wingImplement;
    }
// 실행 클래스
public class Launcher {
    public static void main(String[] args) {
        Airplane airplane = new Airplane(new PowerArmorOnDefaultCockpit());
        airplane.wheelStatusChange("대기");
        airplane.engineStatusChange("대기");
        airplane.electronicStatusChange("대기");
        airplane.wingStatusChange("대기");
    }
}

[이 코드의 문제점]

  • if문의 로직에 대한 중복이 많다.
  • 비행기가 이륙, 순항, 착륙을 제외하고 전투, 파괴라는 상황이 추가되면, 각 메서드별로 if문으로 각각 추가해주어야해서 유지 보수가 불편하다.
  • 비행기가 가지고 있는 장비의 상태에서 바퀴, 엔진, 통신 장비, 날개를 제외하고 기관총이라는 장비가 추가되면, 메소드를 추가해야 한다. 그런데 메소드를 추가해서 구현할 때에 비행기의 상황에 따라 다른 수많은 if문을 매번 다시 작성해야 한다. 즉, 중복된 코드가 계속 늘어나게 된다.

State 패턴을 사용해서 위 상황을 해결

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/c4627cb6-9438-442e-969a-8ecce5bf11b3/Untitled.png

1) 각 비행기의 상황에 따른 상태 변경을 한 번에 할 수 있도록 메소드 모아놓는 인터페이스 만들기 → ‘비행기 상태’ 인터페이스 만들기

각 비행기의 상황(대기, 이륙, 순항, 착륙)에 따른 ‘변화시켜야만 하는 상태들’을 설정하는 메소드들을 모아놓는 클래스 그룹을 만들기.

public interface AirplaneState {
    public String checkWheelStatus();
    
    public String checkEngineStatus();

    public String checkElectronicStatus();

    public String checkWingStatus();

    public String checkMachinegunStatus();

    public String currentStatusExplanation();
}

2) 각 상황별로 필요한 상태들을 각각 하나씩의 클래스로 분리하기

// 비행기 - 대기 상황
public class StandbyAirplaneState implements AirplaneState {
    @Override
    public String checkWheelStatus() {
        String result;
        return result;
    }

    @Override
    public String checkEngineStatus() {
        String result = "엔진 꺼져 있는 상태";
        return result;
    }

    @Override
    public String checkElectronicStatus() {
        String result = "통신 장비 꺼져 있는 상태";
        return result;
    }

    @Override
    public String checkWingStatus() {
        String result = "날개 접혀 있는 상태";
        return result;
    }

    @Override
    public String checkMachinegunStatus() {
        String result = "기관총 OFF 상태";
        return result;
    }

    @Override
    public String currentStatusExplanation() {
        return "비행기가 대기 상태입니다.";
    }
}

… 다른 상황에 대한 상태들 클래스는 생략

3) ‘비행기’ 클래스에서 ‘비행기 상태’ 클래스 활용해서 구현

public class Airplane {

    // 추상성이 높은 AirplaneState 객체를 전역변수로 두었다.
    protected AirplaneState airplaneState;

    // '비행기 상태' 객체 리턴(getter)
    public AirplaneState getAirplaneState() {
        return airplaneState;
    }

    // '비행기 상태' 객체 셋팅(setter)
    public void setAirplaneState(AirplaneState airplaneState) {
        this.airplaneState = airplaneState;
    }

    public String checkWheelStatus() {
        String result = airplaneState.checkWheelStatus();
        return result;
    }

    public String checkEngineStatus() {
        String result = airplaneState.checkEngineStatus();
        return result;
    }

    public String checkElectronicStatus() {
        String result = airplaneState.checkElectronicStatus();
        return result;
    }

    public String checkWingStatus() {
        String result = airplaneState.checkWingStatus();
        return result;
    }

    public String checkMachinegunStatus()) {
        String result = airplaneState.checkMachinegunStatus();
        return result;
    }

    public String currentStatusExplanation() {
        String result = airplaneState.currentStatusExplanation();
        return result;
    }
}

4) 메인 클래스에서 사용

public class Launcher {
    public static void main(String[] args) {
        // 비행기 객체 생성
        Airplane airplane = new Airplane(new PowerArmorOnDefaultCockpit());

        // 비행기의 상태 셋팅 -> 대기 상태
        airplane.setAirplaneState(new StandbyAirplaneState());
        
        ...
        // 위에서 비행기의 상태를 셋팅했으므로, 필요로 하는 부분에서 다음과 같이 속성을 사용 하면 됨
        airplane.currentStatusExplanation();
        airplane.checkWheelStatus();
        // ... 등등

        // 비행기의 상태를 교체하고 싶을 때 밑의 코드 한 줄이면 끝이나서 정말 효율적이다. 
        airplane.setAirplaneState(new LandingAirplaneState()); // 착륙 상태로 셋팅
    }
}

State 패턴으로 구현했을 때의 장점

  • 비행기의 상태(대기, 이륙, 순항, 착륙)에 정찰, 전투, 파괴가 추가될 때, 추가적으로 상태 클래스(PatrolAirplane, CombatAirplaneState, DestoryAirplaneState)만 추가하면 된다.

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/bb1ccd77-7103-4403-ad84-02896bc53087/Untitled.png

References