본문 바로가기
Javascript/Node.js

Command Pattern - 행위 디자인 패턴

by v8rus 2022. 2. 14.

명령 (Command)

가장 일반적 정의 : 실행에 필요한 모든 정보들을 캡슐화 하고 이렇게 만든 모든 객체를 명령이라 할 수 있음
함수나 기능을 직접적으로 호출하는 대신 이러한 호출을 수행하려는 의도롤 나라태는 객체를 만듦
"그 의도를 구체화 하여 실제 작업으로 변환하는것을 다른 컴포넌트가 담당"

-명령 : 함수, 함수를 호출하는데 필요한 정보를 캡슐화 하는 객체
-클라이언트 : 명령을 생성하고 호출자에게 제공하는 컴포넌트
-호출자 : 대상에서 명령의 실행을 담당하는 컴포넌트
-대상 : 호출의 주체, 단일한 함수이거나 객체의 멤버 함수일 수도 있음

네 컴포넌트는 패턴을 구현하는 방식에 따라 크게 달라짐
작업을 직접 실행하는 대신 명령 패턴을 사용하는 여러 어플이 있음

-명령은 나중에 실행 예약 가능
-명령은 쉽게 직렬화하여 네트워크로 전송, 이걸로 원격 작업 배포, 브라우저에서 서버로 명령 전송, RPC-원격 프로시저 호출 등의 작업 수행 가능
-명령으로 시스템에서 실행된 모든 작업의 기록을 쉽게 유지 가능
-명령은 데이터를 동기화 하고 충돌을 해결하기 위한 몇몇 알고리듬에서 중요한 부분
-실행 예약된 명령은 취소 가능
-여러 명령을 함께 그룹화 할 수있음 => 원자 트랜잭션을 생성하거나 그룹의 모든 작업을 한번에 실행하는 메커니즘을 구현한데 사용 가능
-중복 제거, 결합 및 분산 등의 일련의 명령에 대해 다양한 종류의 변현 수행, 텍스트 편집 등 실시간 협업 프로그램 기반인 OT (Operational tranformation)과 같은 더 복잡한 알고리듬 적용 가능
    >> OT vs CRDT 는 찾아보세요

위의 목록은 네트워킹 및 비동기 실행이 핵심인 Node 같은 플릿폼에서 이 패턴이 얼마나 중요한지 보여줌

 

 

1. 작업 패턴

가장 기본적이고 사소한 구현, 작업 패턴
JS 에서 호출을 나타내는 객체를 만드는 가장 쉬운 방법 : 함수 주위에 클로저를 만들거나 함수를 바인드 하는것

function createTask(target, ...args) {
  return () => {
    target(...args);
  }
}

위랑 동일

cosnt task = target.bind(null, ...args);

이걸로 별도의 컴포넌트를 통해 작업 실행을 제어하고 예약 할 수 있음, 본질적으로 명령 패턴의 호출자와 동일함

 

 

2. 좀 더 복잡한 명령

실행 취소 및 직렬화 지원하는 예제
Twitter과 유사한 서비스에 상태 업데이트를 전송하는 작은 객체인 명령의 대상 객체부터 시작

// statusUpdateService.js
const statusUpdates = new Map();

export cosnt statusUpdateService = {    // 대상 (Target), 명령패턴의 대상을 나타냄
  postUpdate (status) {
    const id = Math.floor(Math.random() * 1000000);
    statusUpdates.set(id, status);
    console.log(`Status postsed : ${status}`);
    
    return id;
  },

  destroyUpdate (id) => {
    statusUpdates.delete(id);
    console.log(`Status removed : ${id}`);
  }
}

새 상태로 업데이트 하여 게시하도록 하는 명령을 생성하기 위해 팩토리 함수 구현해 봅시다

 

// createPostStatusCmd.js
export function createPostStatusCmd (service, status) {
  let postId = null;

  return {      // 명령
    run () {
      postId = serivce.postUpdate(status);
    },
    undo () {
      if (postId) {
        service.destroyUpdate(postId);
        postId = null;
      }
    },

    serialize () {
      return {type : 'status', action: 'post'. status };
    }
  }
}

앞의 함수는 POST 로 최신 상태를 전달한다는 의도를 모델링한 명령을 생성하는 팩토리
각 명령의은 세가지 기능을구현함
-run() : 호출 시 작업을 시작, Task패턴 구현
-undo() : 실행 후 작업 취소, 단순히 대상 서비스에서destroyUpdate() 함수를 호출
-serialize() : 동일한 명령 객체를 다시 만드는데 필요한 모든 정보를 담고 있는 JSON객체를 만들기 위한 함수

이후 호출자를 만들 수 있지. 생성자와 run() 함수 구현

// invoker.js
export class Invoker {
  constructor () {        // 호출자 Invoker
    this.history = [];
  }

  run (cmd) {
    this.history.push(cmd);
    cmd.run();
    console.log(`Command executed`, cmd.serialize());
  }

  // .. 클래스의 나머지 부분
}

run()함수는 호출자의 기본 기능임 명령을 history 인스턴스 변수에 저장한 다음 명령 자체를 실행시키는 역할

다음으로 명령 실행을 지연 시키는 새로운 함수를 호출자에 추가 할 수 있음

// 위에 추가하면 됨
delay (cmd, delay) {
  setTimeout(() => {
    console.log('Execuint delayed command', cmd.serialize());
    this.run(cmd);
  }, delay);
};

마지막으로 명령을 되돌릴는 undo() 함수 구현

undo () {
  const cmd = this.history.pop();
  cmd.undo();
  console.log('Command undone', cmd.serialize());
}

웹서비스를 이용해 직렬화 한 다음 네트워크를 통해 전송하여 원격 서버에 명령을 실행하도록 할 수 있음

 

async runRemotely (cmd) {
  await superagent
    .post(`http://localhost:3000/cmd`).
    .sned({json: cmd.serialize()});

  console.log(`Command executed remotely`, cmd.serialize());
}

명령, 호출자, 대상이 작성되었기 때문에 누락된 유일한 컴포넌트인 clinent.js 
필요한 모든 종속성을 가져와 호출자를 인스턴스화 하여 시작함

 

import {createPostStatusCmd} from './createPostStatusCmd.js';
import {statusUpdateService} from './statusUpdateService.js';
import {Invoker} from './invoker.js';

const invoker = new Invoker();

다음 코드라인을 사용하여 명령을 만들수 있지

const command = createPostStatusCmd(statusUpdateService, 'HI!');

이제 상태 메시지 게시를 나타내는 명령을 가지게 되었고 다음 코드로 바로 명령을 실행 시킬 수 있음

invoker.run(command);
// 되돌리는 방법?
invoker.undo();
// 3초후 메시지 예약
invoker.delay(command, 1000* 3);
// 다른 컴퓨터로 마이그레이션, 어플 부하 분산
invoker.runRemotely(command);

 

3. Summary

예시로 작업을 감싸는 것으로 어떤 작업이 가능한지 보여줬음
중요한것은 반드시 필요한 경우에만 완벽한 명령 패턴을 사용해야 함
함수 호출을 위하 너무 많은 코드가 필요함. 단지 호출 뿐이라면 복잡한 명령은 불필요한 노동
작업실행을 예약하거나, 비동기 작업을 실행해야 하는 경우 간단한 작업 패턴이 최상의 절충안임
대신 실행 취소, 변환 , 충돌 해결, 앞의 사례와 같은 고급기능이 필요한 경우 복잡한 명령 패턴 사용을 권장

 

댓글