본문 바로가기

디자인패턴

상태(State) 패턴

728x90

❓ 사용 이유

  • 특정 객체가 논리적으로 상태를 갖고 그 상태에 따라서 다른 동작을 해야 할 경우
  • 상태를 여러개를 갖고있어 다중 분기처리를 해야하는 경우

🤔 의도

  • 객체의 상태에 따라서 다른 동작을 하도록 한다.
  • 전력패턴과 달리 클라이언트가 해당 전략을 선택하는게 아니라 상태가 내부에서 바뀌도록 함

💻 구현

  • Context 인터페이스와 State인터페이스를 갖는다.
  • 클라이언트는 Context 인터페이스를 사용하고, Context 구현체는 State 인터페이스에 기능을 위임한다.
  • Context 인터페이스에 changeState를 구현하지 않고 내부안에서 변경 할 수 있도록 한다.
  • State 구현체들은 Context의 State를 해당 동작과 함께 변경하도록 한다. ( Context의 State는 State 구현체들에게 위임한다)

SocketContext 인터페이스

  • 클라이언트가 사용할 인터페이스로 소켓 바인딩, 연결, 해제 기능을 제공합니다.
package statepattern.context;

public interface SocketContext {
    void bind();
    void connect();
    void disconnect();
}

SocketChanger 인터페이스

  • SocketState 인터페이스에서 내부 context의 state를 변경하기위해 제공되는 인터페이스입니다.
package statepattern.context;

import statepattern.state.SocketState;

public interface SocketChanger {
    void changeState(SocketState state);
}

SocketContextImpl 구현체

  • 소켓 상태를 내부에 갖고있으며 소켓 상태에 클라이언트가 요청한 동작을 상태에 위임합니다.
  • 생성자를 private로 감추고 정적 팩터리 메서드를 활용하여 SocketContext의 인터페이스 타입만 반환하도록 합니다. ( 클라이언트에서 SocketChanger 를 감추기 위해 의도 하였습니다.)
package statepattern.context;

import statepattern.state.SocketState;
import statepattern.state.SocketStateClosed;

public class SocketContextImpl implements SocketContext, SocketChanger {

    private SocketState state;
    private SocketContextImpl(SocketState state) {
        this.state = state;
    }

    public static SocketContext newInstance()
    {
        return new SocketContextImpl(SocketStateClosed.INSTANCE);
    }

    @Override
    public void bind() {
        state.bind(this);
    }

    @Override
    public void connect() {
        state.connect(this);
    }

    @Override
    public void disconnect() {
        state.disconnect(this);
    }

    @Override
    public void changeState(SocketState state) {
        this.state = state;
    }
}

SocketState 인터페이스

  • SocketStateEstablished, SocketStateConnected, SocketStateClosed가 구현할 인터페이스 입니다.
package statepattern.state;

import statepattern.context.SocketChanger;

public interface SocketState {
    void bind(SocketChanger ctx);
    void connect(SocketChanger ctx);
    void disconnect(SocketChanger ctx);
}

SocketStateEstablished, SocketStateConnted, SocketStateClosed 구현체

  • 상태는 보통 싱글턴으로 사용하기때문에 생성자를 숨기고 정적상수로 인스턴스를 제공합니다.
  • 해당 상태에 맞는 구현을 합니다.
  • ctx를 통해서 내부에서 상태를 변경하도록 합니다.
package statepattern.state;

import statepattern.context.SocketChanger;

public class SocketStateEstablished implements SocketState {

    public final static SocketState INSTANCE = new SocketStateEstablished();

    private SocketStateEstablished() {}
    @Override
    public void connect(SocketChanger ctx) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("SocketState Connected Successfully");
        ctx.changeState(SocketStateConnected.INSTANCE);
    }

    @Override
    public void disconnect(SocketChanger ctx) {
        System.out.println("SocketState Disconnected Successfully");
        ctx.changeState(SocketStateClosed.INSTANCE);
    }
    @Override
    public void bind(SocketChanger ctx) {
        throw new UnsupportedOperationException("SocketState is Established Status");
    }
    
}
package statepattern.state;

import statepattern.context.SocketChanger;

public class SocketStateConnected implements SocketState {
    public static final SocketState INSTANCE = new SocketStateConnected();

    private SocketStateConnected() {}

    @Override
    public void connect(SocketChanger ctx) {
        throw new UnsupportedOperationException("SocketState is Already Connected'");
    }

    @Override
    public void disconnect(SocketChanger ctx) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("SocketState Disconnected Successfully");
        ctx.changeState(SocketStateClosed.INSTANCE);
    }

    @Override
    public void bind(SocketChanger ctx) {
        throw new UnsupportedOperationException("SocketState is Connected Status");
    }
    
}

package statepattern.state;

import statepattern.context.SocketChanger;

public class SocketStateClosed implements SocketState {

    public static final SocketState INSTANCE = new SocketStateClosed();

    private SocketStateClosed() {}

    @Override
    public void connect(SocketChanger ctx) {
        throw new UnsupportedOperationException("SocketState is Closed Status Please bind socket");
    }

    @Override
    public void disconnect(SocketChanger ctx) {
        throw new UnsupportedOperationException("SocketState is Already Closed");
    }

    @Override
    public void bind(SocketChanger ctx) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("SocketState Binded Successfully");
        ctx.changeState(SocketStateEstablished.INSTANCE);
    }

}

Main 실행

package statepattern;
import statepattern.context.SocketContext;
import statepattern.context.SocketContextImpl;

public class Main {
    public static void main(String argv[])
    {
        SocketContext context = SocketContextImpl.newInstance();
        context.bind();
        context.connect();
        context.disconnect();

        try {
            context.connect();
        } catch(UnsupportedOperationException ex) {
            System.out.println(ex.getMessage());
        }

        try {
            context.disconnect();
        } catch(UnsupportedOperationException ex) {
            System.out.println(ex.getMessage());
        }
    }
}

🎯 개인적 견해

  • 기능을 상태에 위임하기때문에 SRP 원칙을 지킬 수 있다.
  • 상태가 더 추가된다면 OCP 원칙을 지킬 수 있다.
  • 클라이언트는 복잡한 상태를 관리하지 않아도 된다.
  • 클라이언트는 안에있는 상태를 잘 모르기때문에 어느 시점에 어떤 API를 호출해야할지 어렵다.
    • 현재 상태를 확인하는 인터페이스도 필요할 것 같다.
728x90

'디자인패턴' 카테고리의 다른 글

방문자(Visitor) 패턴  (0) 2024.02.12