[Java] 디자인 패턴, State에 대해 알아보자

     

    📝 State 정리

    • 행동 패턴 디자인입니다.
    • 객체의 내부 상태가 변경될 때마다 객체의 행동을 변경할 수 있도록 합니다.
    • 객체의 상태를 클래스로 캡슐화합니다.
    • 상태에 따라 동일한 작업을 다르게 처리할 수 있습니다.
    • 코드의 유지보수를 용이하게 만들 수 있고, 객체 지향 원칙에 보다 잘 부합하게 해 줍니다.

     

    디자인패턴 중, 가장 잘 나오는 기능 중 하나는  동적바인딩입니다.

    동적바인딩을 사용하는 이유에는 대표적으로 Strategy와 Command 패턴이 있다고 생각됩니다.

     

    (Strategy) 전략적으로 사용할 것인지, (Command) 여럿 선택 중 하나를 선택할 것인지에 대한 선택입니다.

    여기서, 동적바인딩을 Main에서 굳이 할 필요가 있을까?

    Strategy패턴에서의 동적 바인딩 사용 모습

    동적바인딩으로 인해서, Main의 코드가 길어지는 모습을 볼 수 있습니다.

     

    Main 함수 부분을 최소화시키자, 즉 실행코드에서 동적바인딩 사용코드를 최소화시키는 것에 주목적을 지니고 있습니다.

     

     

     

    📑 구성 요소

    요소 설명
    Context
    • 사용자 또는 클라이언트와 상호작용하는 주 객체이다.
    • 현재 상태를 가지고 있다.
    • 상태에 따라서 행동이 변화된다.
    • Context는 상태 변화를 처리하기 위해 상태 객체에 작업을 위임한다.
    State ( 상태 )
    • 각각의 특정 상태를 나타내는 인터페이스 또는 추상 클래스
    • 여러 구체적인 상태 클래스가 이를 구현하거나 상속받아 각 상태에서의 특정 행동을 정의
    Concrete State
    ( 구체적 상태 )
    • State 인터페이스를 구현하는 클래스
    • 특정 상태에서의 실제 행동을 구현
    • Context 객체의 요청을 처리

     

     

    📑 State 장, 단점

    🍖 장점

    • 상태에 따른 행동의 변경을 로직화, 각 상태를 독립적인 클래스로 관리
    • 상태 추가 및 변경이 용이, 기존 코드를 변경하지 않고 새로운 상태 클래스를 추가할 수 있다.
    • 조건문을 줄임으로써, 코드의 복잡함을 감소

    〽️ 단점

    • 많은 수의 상태가 존재했을 때, 시스템에 많은 클래스가 추가되어 관리의 복잡해진다.

     

     

     🧑‍💻 코드 보기

    🍀 State

    public interface State {
        //변경 함수
        void handle(Context context);
    
        //실제 사용 함수
        void execute();
    }

     

     

    🍀 Context

    public class Context {
        private State state;
    
        // 변경로직에서, 변경 될 State값 담는 함수
        public void setState(State state) {
            this.state = state;
        }
    
        // 변경 로직
        public void request() {
            state.handle(this);
        }
    
        // State 기능 구현 1
        public void execute() {
            state.execute();
        }
    }

     

     

    🍀 ConcreteStateA

    public class ConcreteStateA implements State{
    
        @Override
        public void handle(Context context) {
            execute();
            context.setState(new ConcreteStateB());
        }
    
        @Override
        public void execute() {
            System.out.println("Context A 사용 영역");
        }
    }

     

     

    🍀 ConcreteStateB

    public class ConcreteStateB implements State{
    
        @Override
        public void handle(Context context) {
            execute();
            context.setState(new ConcreteStateC());
        }
    
        @Override
        public void execute() {
            System.out.println("Context B 사용 영역");
        }
    }

     

     

    🍀 ConcreteStateC

    public class ConcreteStateC implements State{
    
        @Override
        public void handle(Context context) {
            execute();
            context.setState(new ConcreteStateA());
        }
    
        @Override
        public void execute() {
            System.out.println("Context C 사용 영역");
        }
    }

     

     

    🔧 Main

    public class Main {
        public static void main(String[] args) {
    
            Context context = new Context();
            context.setState(new ConcreteStateA());
            context.request();
            context.execute();
            context.request();
            context.request();
        }
    }

     

     

    🔍 체크 사항

    1. State와 Context는 서로가 참조하는 방식을 사용한다.

    • State는 변경된 것을 Context에게 알리기 위해서 사용된다.
    • Context는 변경 된 State를 사용하기 위해서 사용된다.
    • Context가 setter를 만들면, State가 변경된 값을 다시 넣는 방식이다.

    2. State의 handle

    • 사용된 State는 익명 클래스(Anonymus Class)이므로, 변경 전 클래스는 stack과 heap의 연결관계가 끊기면서 가비지컬렉터에 의해서 사라진다.
    • 변경 후 클래스 또한 익명 클래스 (Anonymus Class) 이므로, 언제든지 사용되고 버릴 수 있게 된다.
    • 카드의 셔플 같은 원리이다.

     

    🐳 마무리

    서로의 객체가 서로를 주입하는 재밌는 패턴이 생겨났다.

    처음 코드를 작성했을 때, 무슨 이런 코드가 있을까... 생각이 잠시 들었지만

    Stack과 Heap의 기본 원리를 가진 컴퓨터 언어에서 서로 간의 연결고리를 유지하기 위한 선택이 되었다.

     

    단순하면서도, 다시 봤을 때 헷갈릴 수 있으므로 주의하도록 하자.

     

     

    반응형

    댓글

    Designed by JB FACTORY