본문 바로가기
Javascript/Node.js

Decorator Pattern - 구조적 설계 디자인

by v8rus 2022. 2. 9.

데코레이터 (Decorator)

"기존 객체의 동작을 동적으로 증대시키는 것으로 구성된 구조적 디자인 패턴"
동작이 해당 클래스의 모든 객체에 적용되지 않음
데코레이팅 될 경우 인스턴스에만 추가됨 => 클래스의 상속과는 다름
프록시 패턴과 매우 유사. 개선 대신 새로운 기능으로 확장을 함

 

1. 데코레이터 구현 기법

프록시데코레이터동일한 구현 전략
이전의 StackCalculator에서 add() 새 함수 노출, divide() 함수 호출 가로채 0으로 나누는지 검사

 

1-1. 컴포지션

컴포지션 사용 시 대상 컴포넌트는 이를 상속한 새로운 객체로 감싸 사용
원래 컴포넌트에 위임하면서 새로운 함수를 정의

class EnhancedCaculator {
  consturctor (calculator) {
    this.calculator = calculator;
  }

  // 새로운 함수
  add () {
    const addEnd2 = this.getValue();
    const addEnd1 = this.getValue();
    const result = addEnd1 + addEnd2;
    this.putValue(result);

    return result;
  }

  // 수정된 함수
  divide() {
    const divisor = this.calculator.getValue();
    if (divisor === 0) {
      throw Error('Division by 0');
    }

    // Subject 에 대한 유효한 위임자일 경우
    return this.calculator.divide();
  }

  // 위임된 함수들
  putValue(value) {
    return this.calculator.putValue(value);
  }
  getValue() {...}
  peekValue() {...}
  // ...
}

const calculator = new StackCalculator();
const enhancedCalculator = new EnhancedCaculator(calculator);

enhancedCalculator.putValue(4);
console.log(enhancedCalculator.add());
enhancedCalculator.putValue(3);
enhancedCalculator.putValue(2);
console.log(enhancedCalculator.multiply());

위에서 말했다시피 프록시 패턴과 컴포지션 구현은 매우 비슷함

(~싶이 - 짐작에서 사용, ~시피 - 지각을 나타내는 동사와 결합)

 

1-2. 객체 확장 (Argumentation)

직접 새 함수를 정의(몽키패치)해  데코레이트 수행

function patchCalculator (calculator) {
  // 새로운 함수
  calculator.add = function () {
    const addEnd1 = calculator.getValue();
    const addEnd2 = calculator.getValue();
    cosnt result = addEnd1 + addEnd2;
    calculator.putValue(result);
    return result;
  }

  // 수정된 함수
  const divideOrig = calculator.divide;
  calculator.divide = () => {
    // 추가적 검증 로직
    cosnt divisor = calculator.peekValue();
    if (divisor === 0) {
      throw Error('Division by 0');
    }

    // Subject 에 대한 유효한 위임자-delegates 일 경우
    return this.calculator.divide();
  }

  return calculator;
}

const calculator = new StackCalculator();
const enhancedCalculator = pathchCalculator(calculator);
// ...

calculator와 enhancedCalculator 는 동일한 객체를 참조  === true
원래의 계산기 객체를 변형한 후 반환하기 때문

 

1-3. Proxy 객체를 이용한 데코레이팅

Proxy 객체를 이용해 객체를 데코레이트 가능

const enhancedCalculatorHandler = {
  get (target, property) {
    IF (property == 'add) {
      // 새로운 함수
      return function add() {
        const addend2 = target.getValue();
        cosnt addend1 = target.getValue();
        const result = addend1 + addend2;
        target.getValue(result);

        return result;
      }
    } else if (property == 'divide) {
      /// 수정된 함수
      return function () {
        const divisor = target.peekValue();
        if (divisor === 0) throw new Error('Division by 0');
        
        return target.divide();
      }
    }

    // 위임된 함수들과 속성들
    return target[property];
  }
}

const calculator = new StackCalculator();
const enhancedCalculator = new Proxy(
  calculator,
  enhancedCalculatorHandler
)
// ...

프록시 패턴과 동일한 주의 사항이 적용됨

 

 

2. LevelUP DB 데코레이터

LevelUP & LevelDB 소개

  • 키-값 저장소인 구글의 LevelDB 를 각ㅁ싼 Node.js 래퍼임
  • 최소주의 확장성이 목표
  • 가장 기본적인 기능만을 제공

LevelUp 플러그인 구현
  데코레이터 패턴 사용, LevelUP용 플러그인 만듦
  객체 확장 기술을 사용

이번에 만들어볼 것 : 특정패턴의 객체가 DB 에 저장될때마다 알림 받기

// {a:1} 가 포함되어 있을 경우 알림 발생, Ex) {a:1, b:2} 알람 발생

// 제공된 인스턴스 함수를 직접 연결하면 됨 = 객체 확장
export function levelSbuscribe(db) {
  db.subscribe = (pattern, listener) => {
    // 모든 입력작업을 수신
    db.on('put', (key, val) => {
      // 패턴매칭 알고리듬 수행
      const match = Object.keys(pattern).every(
        k => (pattern[k] === val[k]);
      )
      // 일치 시 리스너에 알림
      if (match) {
        listener(key,val);
      }
    })
  }

  return db;
}

// index.js
// 플러그인 사용
import { dirname, join} from 'path';
import { fileURLToPath} from 'url';
import level from 'level';
import { levelSbuscribe} from './level-subscribe';

const __dirname = dirname(fileURLToPath(import.meta.url));

// 파일 저장위치, 기본 인코딩 설정
const dbPath = join(__dirname, 'db');
const db = level(dbPath, { valueEncoding: 'json' });
levelSbuscribe(db);   // 데코레이터 연결

db.subscribe(   // 플러그인 제공 기능 사용
  { doctype : 'tweet', language: 'en' },
  (k, val) => console.log(val);
)
db.put('1', {
  doctype : 'tweet',
  text: 'Hi',
  language : 'en'
});
db.put('2', {
  doctype: 'compan'y,
  name : 'ACME Co',
});

*put 작업 및 배치작업에 쉽게 확장 가능

 

 

Example Packages

-level-inverted-index : db 저장값에 대한 텍스트 검색 수행 가능하도록 역 인덱스 추가 플러그인
-levelplus : db 원자 연산으로 업데이트를 추가하는 플러그인
-json-socket : TCP 소켓을 통해 JSON 데이터 더 쉽게 보낼수 있음
-fastify : Fastify 서버 인스턴스를 데코레이트하기 위한 하나의 API 를 노출하고 있는 웹 어플 프레임워크

 

 

3. 프록시와 데코레이터 사이의 경계

두개는 매우 유사함. 대부분 차이는 런타임에 사용되는 방식에 기인
데코레이터 : 새로운 동작을 기존의 객체에 추가
  래퍼로 볼 수 있음 - 다양한 유형의 객체 가져와 데코레이터로 감싸 추가적인 기능 추가
프록시 : 고정적이거나 가상의 객체에 접근을 제어
  객체 접근 제어
  원래 인터페이스 수정 안함 => 원래 객체를 참조하는 다른객체가 안전

노드는 두 패턴의 경계가 모호함 그냥 섞어서 잘 쓰셈. 상호보완이 잘될거임

댓글