상태 (State) 패턴
1. 의도
- 객체 상태에 따라 행동이 다르고 행동은 다른 상태 전이를 발생시킬때 사용하는 패턴.
2. 활용
- TCP 연결
- 네트워크 연결을 추상화한 TCPConnection 클래스를 생각해보면 TCPConnection 클래스는 여러가지 상태를 가질 수 있다.
- 연결 성공(Established), 대기(Listening), 연결 종료(Closed) 상태를 가지는데 TCP-Connection 객체는 자신의 상태에 따라 사용자 요청에 대한 처리 결과가 다르다.
- ex) Open 요청은 종료 상태에서만 의미가 있을 수 있고 Acknowledge() 요청은 전송된 패킷을 성공적으로 수신했다는 패킷을 보내는 것이므로 대기나 연결 성공 상태에서 의미가 있는 연산이다.
- TCPState 추상 클래스
- TCP 추상 클래스는 네트워크 연결에서 나타나는 모든 상태를 표현하고 가능한 요청에 대한 인터페이스를 제공한다.
- TCPState의 서브 클래스는 각 자신의 상태에 따른 요청 방식을 구체적으로 구현하도록 한다.
- TCPConnection 클래스
- TCPConnection 클래스는 사용자의 인터페이스에 가까운 클래스로 TCPState 인스턴스를 가짐으로 자신의 상태를 표현하고 TCPState의 인터페이스와 같기 때문에 실제 구현은 TCPState 인스턴스의 연산을 이용한다.
- 행동 결과에 따라 TCPState 서브 클래스 중 하나의 인스턴스로 변경한다.
다음 상황에서 상태 패턴을 사용한다.
- 객체의 행동이 상태에 따라 달라지고 상태에 따라서 런타임에 행동이 바뀌어야한다.
- 상태에 따라 행동을 선택해야하는데 이를 위한 분기 조건 (if-else, switch)이 많이 사용될 때
3. 참여자
- Context: 사용자가 관심있는 인터페이스르 정의
- State: Context의 각 상태별로 필요한 행동을 캡슐화하여 인터페이스로 정의
- ConcreteState 서브클래스: 각 서브 클래스들은 Context의 상태에 따라 처리되어야할 실제 행동을 구현
4. 협력방법
- Context가 요청을 받으면 현재의 ConcreteState 객체에 요청을 전달한다.
- Context클래스는 실제 연산을 처리할 State 객체에 자신을 매개변수로 전달한다. (자신에 대한 정보 접근을 위해)
- Context 클래스는 사용자가 사용할 수 있는 기본 인터페이스를 제공한다.
- Context 클래스 또는 CocnreteState 서브클래스는 다음 자기상태가 무엇이고, 어떤 환경에서 다음 상태로 가는 지 결정 가능하다.
5. 결과
1) 상태에 따른 행동을 제한하고 서로 다른 상태에 대한 행동을 별도의 객체로 관리한다.
- 새로운 상태와 전이 규칙이 있다면 새로운 서브클래스만 정의하면 된다.
- 상태 종류가 많아지면 서브 클래스가 많아지지만 상태 클래스를 두지 않는다면 조건문 검사도 많이 필요하게 된다.
2) 상태 전이를 명확하게 만든다.
- 상태를 내부 일반 데이터로 표현하지 않고 객체로 관리하며 행동 구현이 되어 있기 때문에 상태 전이를 명확하게 할 수 있다.
3) 상태 객체는 공유될 수 있다.
- 모든 Context에 공통적으로 적용될 수 있는 상태이기 때문에 같이 공유할 수 있다.
6. 구현
1) 누가 상태 전이를 정할 것인가
- State의 서브 클래스들이 다음 상태를 알고 있으므로 State가 전이를 담당한다. 이 경우 State의 상태 전이 연산에 Context를 매개 변수로 전달하여 Context의 상태를 변경할 수 있도록 한다.
2) 테이블 기반
- 상태와 전이된 상태를 관리한다. 조건식보다는 괜찮지만 여전히 테이블 탐색을 해야하고 전이된 상태를 바로 알기 힘들기 때문에 상태 전이에 따른 처리 동작을 추가하기 힘들다.
3) 상태 객체의 생성과 소멸
- 상태 객체를 필요할 때만 생성하고 필요 없게 되면 없애거나 필요하기 전에 미리 만들어두고 없애지 않고 계속 두는 방법.
- State 객체가 정보가 많다면 사용하는 객체만 생성한다. 반대로 상태 전이가 빈번하다면 생성-삭제에 오버헤드가 있기 때문에 미리 만들어두고 공유하는 방법이 좋다.
4) 동적 상속
- 클래스를 아에 변경해서 상태를 변경한다. 거의 사용되지 않는다.
예제 코드
TCP 프로토콜을 사용한 네트워크 연결 수립 과정 중 나타나는 TCPState와 서브클래스를 구현해보고 TCPConnection 클래스는 사용자 인터페이스르 제공한다.
연관 내용: TCP의 3-way handshaking 연결 수립과정
2021/01/06 - [Computer Network] - [네트워크] 전송 계층 (2) TCP 프로토콜 / 혼잡 제어
TCPConnection.h
- TCPConnection 클래스는 사용자 인터페이스를 제공하며 Open(), Close(), Send()와 같은 연산과 Acknowldeg(), Synchronize()와 같이 ACK, SYN를 위한 더 작은 단위의 연산이 있다.
- ProcessOctect()은 연결되어 있는 소켓을 통해 패킷을 전송한다.
- TCPState를 friend 클래스로 지정하여 TCPState 서브 클래스가 자신의 ChangeState 메서드를 사용하도록 허락하여 자신의 TCPState 상태를 변경하도록 한다.
class TCPOctetStream;
class TCPState;
class TCPConnection {
public:
TCPConnection();
// 상태 인스턴스의 연산을 이용
void ActiveOpen() { _state->ActiveOpen(this); }
void PassiveOpen() { _state->PassiveOpen(this); }
void Close() { _state->Close(this); }
void Send() { _state->Send(this); }
void Acknowledge() { _state->Acknowledge(this); }
void Synchronize() { _state->Synchronize(this); }
// 소켓 전송을 위한 데이터 처리
void ProcessOctet(TCPOctetStream*);
private:
// TCPState 인스턴스가 자신의 ChangeState를 접근할 수 있게함
friend class TCPState;
// 상태 변경
void ChangeState(TCPState* s) { _state = s; }
private:
TCPState* _state;
};
TCPState.h
- 모든 연산의 기본 구현은 비워두고 상태에 따른 행동을 자신의 서브 클래스가 구현하도록 한다.
- TCPConnection과 같은 인터페이스를 제공하며 모든 상태를 구현할 필요는 없기 때문에 순수 가상 함수로 정의하지 않는다.
- ChangeState()는 TCPConnection 인스턴스와 다음 상태를 매개변수로 받아 상태를 변경한다.
class TCPState
{
public:
// 기본 구현은 없음 (상태마다 제공하는 연산이 다름)
virtual void Transmit(TCPConnection*, TCPOctetStream*){}
virtual void ActiveOpen(TCPConnection*){}
virtual void PassiveOpen(TCPConnection*){}
virtual void Close(TCPConnection*){}
virtual void Synchronize(TCPConnection*){}
virtual void Acknowledge(TCPConnection*){}
virtual void Send(TCPConnection*){}
protected:
void ChangeState(TCPConnection* t, TCPState* s) { t->ChangeState(s); }
};
TCPState의 서브 클래스
기본 상태 전이는 위와 같이 표현할 수 있다.
이에 따른 서브 클래스의 각 상태 전이를 동반하는 함수를 구현하도록 한다.
TCPState는 어떤 TCPConnection 인스턴스가 사용하던지 상관 없기 때문에 단일체로 구현하여 공유할 수 있다.
- TCPClosed.h
기본 상태는 닫힘 상태에서 시작한다.
- PassiveOpen()을 통해 TCPListen 상태로 전이 가능하다.
- ActiveOpen()은 중간 SYN와 SYNACK를 받는 과정을 포함하고 바로 연결 수립 상태로 전이가능하다.
class TCPClosed : public TCPState
{
public:
static TCPClosed* Instance();
virtual void ActiveOpen(TCPConnection*);
virtual void PassiveOpen(TCPConnection*);
// ...
private:
TCPClosed();
static TCPClosed* _instance;
};
TCPClosed* TCPClosed::_instance = 0;
void TCPClosed::ActiveOpen(TCPConnection* t)
{
// send SYN, receive SYNACK
ChangeState(t, TCPEstablished::Instance());
}
void TCPClosed::PassiveOpen(TCPConnection* t)
{
// socket listens to connect
ChangeState(t, TCPListen::Instance());
}
- TCP Listen.h
- TCP 소켓이 연결 수립되기 전 SYN 메세지를 보내는 중간 단계이다.
- Send()를 통해 SYN를 전송, SYNACK를 수신받도록하고 연결 수립이 될 수 있다.
class TCPListen : public TCPState
{
public:
static TCPState* Instance();
virtual void Send(TCPConnection*);
// ...
private:
TCPListen();
static TCPState* _instance;
};
TCPState* TCPListen::_instance = 0;
void TCPListen::Send(TCPConnection* t)
{
// send SYN, receive SYNACK
ChangeState(t, TCPEstablished::Instance());
}
- TCPEstablished.h
- Transmit() 전송은 보낼 패킷을 소켓을 통해 보내는 과정이며 연결 상태를 유지한다.
- 소켓을 닫는 Close() 연산시 TCPClosed 상태가 된다.
class TCPEstablished : public TCPState
{
public:
static TCPState* Instance();
virtual void Transmit(TCPConnection*, TCPOctetStream*);
virtual void Close(TCPConnection*);
private:
static TCPEstablished* _instantce;
};
TCPEstablished* TCPEstablished::_instantce = 0;
void TCPEstablished::Transmit(TCPConnection* t, TCPOctetStream* o)
{
t->ProcessOctet(o);
}
void TCPEstablished::Close(TCPConnection* t)
{
// send FIN, receive ACK of FIN
ChangeState(t, TCPListen::Instance());
}
'디자인 패턴 > GoF' 카테고리의 다른 글
[디자인 패턴] 행동 패턴 (6) 전략 Strategy (0) | 2021.03.13 |
---|---|
[디자인 패턴] 행동 패턴 (5) 감시자 Observer (0) | 2021.02.04 |
[디자인 패턴] 행동 패턴 (3) 해석자 Interpreter (0) | 2021.02.02 |
[디자인 패턴] 행동 패턴 (2) 명령 Command (0) | 2021.02.02 |
[디자인 패턴] 행동 패턴 (1) 책임 연쇄 Chain of Responsiblity (0) | 2021.02.01 |