ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • State Pattern(상태 패턴)
    STUDY/디자인패턴 2025. 2. 16. 22:54

    상태 패턴이란?

    객체의 내부 상태가 변경될 때 해당 객체가 그의 행동을 변경할 수 있도록 하는 행동 디자인 패턴으로, 객체가 행동을 변경할 때 객체가 클래스를 변경한 것처럼 보일 수 있다.

     

    상태 패턴은 객체의 모든 가능한 상태들에 대해 새 클래스들을 만들고 모든 상태별 행동들을 이러한 클래스들로 추출한다. 콘텍스트라는 원래 객체는 모든 행동을 자체적으로 구현하는 대신 현재 상태를 나타내는 상태 객체 중 하나에 대한 참조를 저장하고 모든 상태와 관련된 작업을 그 객체에 위임한다.

     

    구조

    • Context

    구상 상태 객체 중 하나에 대한 참조를 저장하고 모든 상태별 작업을 구상 상태 객체에 위임한다. 콘텍스트는 상태 인터페이스를 통해 상태 객체와 통신하며, 새로운 상태 객체를 전달하기 위한 세터를 노출한다.

     

    • State

    상태별 메서드들을 선언한 인터페이스로, 이러한 메서드들은 모든 구상 상태에서 유효해야 한다.

     

    • ConcreteStates

    상태별 메서드들에 대한 자체적인 구현을 제공하는 구상 클래스로, 여러 상태에서 유사한 코드의 중복을 피하기 위하여 어떤 공통 행동을 캡슐화하는 중간 추상 클래스들을 제공할 수 있다. 상태 객체들은 콘텍스트 객체에 대한 역참조를 저장할 수 있고, 이 참조를 통해 상태는 콘텍스트 객체에서 모든 필요한 정보를 가져올 수 있고 상태 천이를 시작할 수 있다.

     

    콘텍스트와 구상 상태들은 모두 콘텍스트의 다음 상태를 설정할 수 있으며, 콘텍스트에 연결된 상태 객체를 교체하여 실제 상태 천이를 수행할 수 있다.

     

    예시

    State

    public abstract class State {
        Player player;
    
        State(Player player) {
            this.player = player;
        }
    
        public abstract String onLock();
        public abstract String onPlay();
        public abstract String onNext();
        public abstract String onPrevious();
    }

     

    ConcreteStates

    public class LockedState extends State {
    
        LockedState(Player player) {
            super(player);
            player.setPlaying(false);
        }
    
        @Override
        public String onLock() {
            if (player.isPlaying()) {
                player.changeState(new ReadyState(player));
                return "Stop playing";
            } else {
                return "Locked...";
            }
        }
    
        @Override
        public String onPlay() {
            player.changeState(new ReadyState(player));
            return "Ready";
        }
    
        @Override
        public String onNext() {
            return "Locked...";
        }
    
        @Override
        public String onPrevious() {
            return "Locked...";
        }
    }
    public class ReadyState extends State {
    
        public ReadyState(Player player) {
            super(player);
        }
    
        @Override
        public String onLock() {
            player.changeState(new LockedState(player));
            return "Locked...";
        }
    
        @Override
        public String onPlay() {
            String action = player.startPlayback();
            player.changeState(new PlayingState(player));
            return action;
        }
    
        @Override
        public String onNext() {
            return "Locked...";
        }
    
        @Override
        public String onPrevious() {
            return "Locked...";
        }
    }
    public class PlayingState extends State {
    
        PlayingState(Player player) {
            super(player);
        }
    
        @Override
        public String onLock() {
            player.changeState(new LockedState(player));
            player.setCurrentTrackAfterStop();
            return "Stop playing";
        }
    
        @Override
        public String onPlay() {
            player.changeState(new ReadyState(player));
            return "Paused...";
        }
    
        @Override
        public String onNext() {
            return player.nextTrack();
        }
    
        @Override
        public String onPrevious() {
            return player.previousTrack();
        }
    }

     

    Context

    public class Player {
        private State state;
        private boolean playing = false;
        private List<String> playlist = new ArrayList<>();
        private int currentTrack = 0;
    
        public Player() {
            this.state = new ReadyState(this);
            setPlaying(true);
            for (int i = 1; i <= 12; i++) {
                playlist.add("Track " + i);
            }
        }
    
        public void changeState(State state) {
            this.state = state;
        }
    
        public State getState() {
            return state;
        }
    
        public void setPlaying(boolean playing) {
            this.playing = playing;
        }
    
        public boolean isPlaying() {
            return playing;
        }
    
        public String startPlayback() {
            return "Playing " + playlist.get(currentTrack);
        }
    
        public String nextTrack() {
            currentTrack++;
            if (currentTrack > playlist.size() - 1) {
                currentTrack = 0;
            }
            return "Playing " + playlist.get(currentTrack);
        }
    
        public String previousTrack() {
            currentTrack--;
            if (currentTrack < 0) {
                currentTrack = playlist.size() - 1;
            }
            return "Playing " + playlist.get(currentTrack);
        }
    
        public void setCurrentTrackAfterStop() {
            this.currentTrack = 0;
        }
    }

     

    적용

    1. 현재 상태에 따라 다르게 행동하는 객체가 있는 경우, 상태들의 수가 많은 경우, 그리고 상태별 코드가 자주 변경되는 경우
    2. 클래스 필드들의 현재 값들에 따라 클래스가 행동하는 방식을 변경하는 거대한 조건문들로 오염된 클래스가 있는 경우
    3. 유사한 상태들에 중복 코드와 조건분-기반 상태 머신의 천이가 많은 경우

     

    장단점

    장점

    • 단일 책임 원칙. 특정 상태들과 관련된 코드를 별도의 클래스로 분리할 수있다.
    • 개방/폐쇄 원칙. 기존 상태 클래스들 또는 콘텍스트를 변경하지 않고 새로운 상태들을 추가할 수 있다.
    • 거대한 상태 머신 조건문들을 제거하여 콘텍스트의 코드를 단순화할 수 있다.

     

    단점

    • 상태 머신에 몇 가지 상태만 있거나 머신이 거의 변경되지 않을 때 상태 패턴을 적용하는 것은 과할 수 있다.
Designed by Tistory.