본문 바로가기
Javascript/Node.js

State Pattern - 행위 디자인 패턴

by v8rus 2022. 2. 9.

상태 (State)

"컨텍스트의 상태에 따라 전략이 변경되는 패턴"

전락 : 환경설정의 속성 또는 입력 인자와 같은 변수에 따라 전략을 선택하는 방법
  => 한번 생성된 컨텍스트에서는 전략이 변경되지 않은 상태로 유지
상태 : 전략은 동적이며 컨텍스트의 생존 주기동안 변경 될 수 있음
  => 서로 다른 컨텍스트상태에서 서로 다른 전략을 선택

Ex) 예약 시스템
-예약 처음 생성시 사용자가 예약을 확인 할 수 있음, 예약하지 않았기 떄문에 예약 O, 취소 X, 구매 이전 삭제 O
-예약 완료 시 취소 O,  삭제 X
-예약일 전날 취소 X, 너무 늦음
패턴을 활용하여 한 동작에서 다른 동작으로 쉽게 전환 가능
각각의 상태 변경에 따라 다른 전략이 필요함

상태 전략은 유연성과 디커플링 측면에서 최상의 결과를 제공

 

1. 기본적인 안전 소켓 구현

서버와 연결이 끊어져도 실패하지않는 TCP 클라이언트 소켓
오프라인 상태일때 모든 데이터를 큐에 넣음
재연결 시 쌓인 데이터 다시 전송 (일련의 시스템이 정기적 리소스 사용 통계 전송, 연결때 까지 데이터 로컬 큐에 담음)
모듈을 만들어 봅시다

 

import { OffLineState } from './offlineState.js';
import { OnlineState } from './onlineState.js';

export class FailstateSocket {
  constructor (options) {
    this.options = options;
    this.queue = [];
    this.currentState = null;
    this.socket = null;
    this.states = {       // 두가지 상태의 집합 참조
      offline: new OfflineState(this),
      online : new OnlineState(this),
    }
    this.changeState('offline');
  }

  changeState (state) {   // 다른 상태로 전환, 인스턴스 변수 업데이트 후 active 호출
    console.log('Activating state : ${state}');
    this.currentState =  this.states[state];
    this.currentState.activate();
  }

  send (data) {
    this.currentState.send(data);   // 현재 활성 상태로 작업으 ㄹ위임하면 됨
  }
}

두 상태가 어떻게 표현되는지 봅시다

// offlineState.js
// 작은 라이브러리 활용, 소켓 통과하는 JSON 객체의 모든 구문 분석 및 형식화 처리
import jsonOverTcp from 'json-over-tcp-2';    

export class OfflineState {
  constructor (failsafeSocket) {
    this.failsafeSocket = failsafeSocket;
  }

  send (data) {   // 단순히 큐에 넣는 작업만 함
    this.failsafeSocket.queue.push(data);
  }

  activate() {
  	// 서버와의 연결을 설정, 실패하면 일정 시간 후 재시도, 연결될때까지 재시도
    // 연결 될 경우 failsafeSocket 상태가 온라인으로 전환
    const retry = () => {     
      setTimeout(() => this.activate(), 1000);
    }

    console.log('Trying to connect...');
    this.failsafeSocket.socket = jsonOverTcp.connect(
      this.failSafeSocket.options,
      () => {
        console.log('conneciton established');
        this.failsafeSocket.socket.removeListener('error', retry);
        this.failsafeSocket.changeState('online');
      }
    )
    this.failsafeSocket.socket.once('error', retry);
  }
}

오프라인 상태일대의 소켓 동작을 관리하는 것이고요

 

// onlineState.js
export class OnlineSate {
  constructor (failsafeSocket) {
    this.failsafeSocket = failsafeSocket;
    this.hasDisconnected = false;
  }

  send (data) {   // 온라인 상태임을가정하고 즉시 소켓에 직접 쓰려함
    this.failSafeSocket.qeueue.push(data);
    this._safeWrite(data);    // 소켓에 쓰는 함수
  }

  // 소켓의 쓰기 가능한 스트림에 데이터 쓰기를 시도, 전송되기를 기다림
  // 성공적으로 반환시 큐에서 제거
  _safeWrite (data) {   
    this.failsafeSocket.socket.write(data, err => {
      if (!this.hasDisconnected && !err) {
        this.failSafeSocket.qeueue.shift();
      }
    })
  }

  activate () {   // 오프라인상태에서 전환시 대기열에 있던 데이터를 모두 비움
    this.hasDisconnected = false;
    for (const data of this.failsafeSocket.queue) {
      this._safeWrite(data);
    }
    
    // 오류가 있으면 에러 메시지 수신, 에러가 있으며 오프라인 전환
    this.failsafeSocket.socket.once('error', () => {
      this.hasDisconnected = true;
      this.failsafeSocket.changeState('offline');
    })
  }
}

오프라인 상태에서 소켓 동작 관리

 

 

이번에는 동작시켜 봅시다

// server.js
import jsonOverTcp from 'json-over-tcp-2';

const server = jsonOverTcp.createServer({ port : 5000 });   // 근데 굳이 포트를 두변 명시할 필요가있나???
server.on('connection', socket => {
  socket.on('data', data => {
    console.log('Client data', data);
  })
});

server.listen(5000, () => {
  console.log('Server started');
});

// client.js
import {FailsafeSocket} from './failsafeSocket';

const failesafeSocket = new FailesafeSocket({ port : 5000 });

setInterval(() => {
  // 현재 메모리 사용량을 전달
  failesafesocket.send(process.memoryUsage())
}, 1000);

FailSafeSocket 객체를 활용하여 매 초마다 메모리 사용량을 전송함

이 샘플은 상태 패턴이 상태에 따라 동작을 조정해야 하는 컴포넌트의 모듈성과 가독성을 높이는데 명확한 예시가 됨
여기서 사용한 샘플은 그냥 예시일 뿐 완벽한 소켓 구현에는 부족합니다.

댓글