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 |
---|