본문 바로가기

Backend/디자인 패턴

Template Pattern (템플릿 패턴)

템플릿 패턴은 알고리즘을 나타내는 부분을 추상 클래스로 캡슐화하는 것으로 구성됩니다.

이 추상클래스의 일부 메서드는 정의되지 않은 채로 남아있으며, 서브 클래스는 이 정의되어있지 않은 메서드를 구현하여
알고리즘의 비어있는 부분을 채울 수 있습니다.

 

import * as fs from 'fs';

abstract class Config {
  protected data: Record<string, any>;

  public read(file: string) {
    this.data = this.deserialize(fs.readFileSync(file, 'utf-8'));
  }

  protected deserialize(data: string): Record<string, any> {
    throw new Error();
  }

  public get(key: string): any {
    return this.data[key];
  }
}

class JsonConfig extends Config {
  protected deserialize(data: string): Record<string, any> {
    return JSON.parse(data);
  }
}

class IniConfig extends Config {
  protected deserialize(data: string): Record<string, any> {
    return ini.parse(data);
  }
}

 

위 예시는 전략 패턴에서 사용했던 예시와 비슷합니다.

추상 클래스로 정의한 Config class 는 deserialize 메서드의 구현 부분이 비어있습니다. 이런 형태로 템플릿을 만들어놓고,

JSON 형식 또는 ini 형식에 대한 deserialize 는 각 클래스에서 구체적인 구현을 제공하는 형태입니다.

전략패턴에서처럼 strategy 를 따로 지정할 필요없이 필요한 기능은 클래스 자체에서 수행됩니다.

 

위에서 적은 것처럼 이 템플릿 패턴이 전략 패턴과 유사하지만 차이점은 구조와 구현에 있습니다.

두 패턴 다 공통 부분을 재사용하면서 알고리즘의 일부분을 변경할 수 있습니다. 다만 전략 패턴은 동적으로 런타임에 변경할 수 있는 반면
템플릿 패턴은 클래스가 정의되는 순간 알고리즘이 완성됩니다.

이러한 차이점을 알고 더 적절한 패턴을 사용하는 것은 결국 개발자의 몫입니다.

 

abstract class CommonHandler {
  protected originRequest: any;
  protected convertedRequest: any;
  
  protected abstract convertRequest(): Promise<any>;
  
  protected async initialize(): Promise<void> {
    await this.convertRequest();
  }

  public async getRequest(): Promise<any> {
    await this.initialize();
    return this.convertedRequest;
  }
  
  ...
}

class AHandler extends CommonHandler {
  protected async convertRequest(): Promise<any> {
    // convert originRequest
  }
}

class BHandler extends CommonHandler {
  protected async convertRequest(): Promise<any> {
    // convert originRequest
  }
}

 

위 코드는 현재 회사에서 템플릿 패턴을 사용하고 있는 부분을 일부 가져와봤는데요.

연동된 곳마다 다른 스펙의 request 를 받고 이를 공통의 convertedRequest 로 변환해야 하는 부분에서, 변환하는 부분의 구현만

다르게 가져가면 되므로 공통의 로직은 CommonHandler 에 모아두고 convertRequest 만 서브 클래스에서 구현하는 형태로 가져가게 되었습니다. 확실히 확장이 필요한 부분만 서브 클래스에서 구현을 가져가면서 다른 부분의 코드 중복을 최소화 할 수 있었죠.

 

하지만 점점 CommonHandler 를 상속받는 Handler 가 늘어나고, converRequest 와 같은 서브 클래스에서 구현해야 할 메서드가
늘어나면서 코드 파악이 갈수록 어려워진다는 단점은 일부 있었습니다. 뭐 단점이라고 하기엔 현 구조에선 어쩔 수 없는 것 같기도 하구요.

이러한 단점은 어떻게 해결해나가야 할 지 고민이 필요한 부분이지만 예시를 들어본 것이니 참고만 하시면 좋을 것 같습니다.