객체지향

템플릿 메소드(Template Method) 패턴 실제 적용 사례 - 디자인 패턴

소행성왕자 2024. 8. 23. 10:52

개요

본 문서는 본인의 개발향상과 기억의 장기화를 위해 기록해두기 위해 사용한 문서로 존칭은 생략하기로 한다.

FX 개발도중 사용자마다 원가 + 마진 + 마크업이란 수수료가 붙는 로직이 있는데 템플릿 메소드 패턴을 사용하여 구현하였다.

템플릿 메소드 패턴을 설명하기 위해 세부 코드는 생략하였고 큰 틀에서 설명하기로 한다.

아래는 공식인데 약식으로 설명한다.

- 일반환율(USDKRW 비롯한 KRW 없는것 즉 USDJPY, EURUSD 등)
- 재정환율(USDKRW 제외한 ~/KRW 있는것 즉 JPYKRW EURKRW 등)


: 일반환율
  .바로환전 
     bid=원가 + 원천마크업 + 바로환전마진
     ask=원가 + 원천마크업 + 바로환전마진
  .현물환
     bid=원가 + 원천마크업 + 마크업 + 스왑 + 마진
     ask=원가 + 원천마크업 + 마크업 + 스왑 + 마진
  .선물환
  .스왑
  
 : 재정환율
  - USDKRW 가격과 이종통화 가격을 조합해서 사용
  - 예를들면 EURKRW 만든다라고 하면 UDKRW 와 EURUSD 가격이 필요함
  .바로환전
  	1.USDKRW 가격 make
       bid = 원가 + 원천마크업 + 마크업 + 스왑
       ask = 원가 + 원천마크업 + 마크업 + 스왑
    2.EURUSD 가격 make
       bid = 원가 + 원천마크업 + 마크업 + 스왑
       ask = 원가 + 원천마크업 + 마크업 + 스왑
    3. 통화별 계산
       - EURKRW, GBPKRW, AUDKRW, NZDKRW 는 곱하기
         bid = USDKRW가격 * EURUSD가격 *1 - 수수료
         ask = USDKRW가격 * EURUSD가격 *1 + 수수료
       - JPYKRW
         bid = USDKRW가격 / EURUSD가격 *1 - 수수료
         ask = USDKRW가격 / EURUSD가격 *1 + 수수료
       - 나머지
         bid = USDKRW가격 / EURUSD가격 *1 - 수수료
         ask = USDKRW가격 / EURUSD가격 *1 + 수수료
    
  .현물환
  .선물환
  .스왑

 

스왑도 기술하고 싶은데 너무 복잡하여 생략하기로 한다.

일반통화 / 재정통화 모두 템플릿 메소드 패턴으로 작성되어 있기때문에 아래 설명을 정독하면 어느정도 감이 올것이다.

실제 코드를 보면서 기억을 되돌려본다.

일반통화 

클래스 다이어그램

+-------------------+
|    GPriceCalc     |
+-------------------+
| + constructor()   |
| + calc()          |
| # _calc()         |
+-------------------+
         ^
         |
         |
+-------------------+     +-------------------+
|    GInstlycalc    |     |     GSpotcalc     |
+-------------------+     +-------------------+
| + constructor()   |     | + constructor()   |
| # _calc()         |     | # _calc()         |
+-------------------+     +-------------------+

이 다이어그램에서

  1. GPriceCalc는 추상 기본 클래스이다:
    • constructor(): 클래스의 생성자
    • calc(): 공통으로 처리되는 템플릿 메서드
    • _calc(): 추상 메서드 (하위 클래스에서 구현해야 함)
  2. GInstlycalc GPriceCalc를 상속받는 구체 클래스이다:
    • constructor(): 클래스의 생성자
    • _calc(): GPriceCalc의 추상 메서드를 구체적으로 구현
  3. GSpotcalc GPriceCalc를 상속받는 구체 클래스이다:
    • constructor(): 클래스의 생성자
    • _calc(): GPriceCalc의 추상 메서드를 구체적으로 구현

다이어그램에서 사용된 표기:

  • +: public 메서드
  • #: protected 메서드
  • 실선 화살표: 상속 관계

기본 클래스(GPriceCalc)가 알고리즘의 골격을 정의하고, 구체 클래스들(GInstlycalc GSpotcalc)이 특정 단계(_calc())를 구현하는 방식을 명확히 표현하고 있다.

전체소스

const GPriceCalc = class {
	constructor(){}
    calc(){
    	// 공통으로 처리
    }
    _calc() {
    	throw 'override';
    }
};

// 바로환전
const GInstlycalc = class exends GPriceCalc {
	constructor(){}
    _calc() {
    	// 실제 계산 부분
    }
};

// 현물환
const GSpotcalc = class exends GPriceCalc {
	constructor(){}
    _calc() {
    	// 실제 계산 부분
    }
};



const calc = new GInstlycalc();
const result = calc.calc();

const calcSpot = new GSpotcalc();
const resultSpot = calcSpot.calc();

 

템플릿 메서드 패턴은 알고리즘의 구조를 메서드에 정의하고, 하위 클래스에서 알고리즘 구조의 변경 없이 알고리즘을 재정의하는 패턴이다.

알고리즘의 뼈대는 추상 클래스에 정의하고, 구체적인 단계는 하위 클래스에서 구현한다.

GPriceCalc 클래스 (추상 기본 클래스)

const GPriceCalc = class {
    constructor(){}
    calc(){
        // 공통으로 처리
    }
    _calc() {
        throw 'override';
    }
};
  • calc() 메서드: 템플릿 메서드로, 공통적인 알고리즘 구조를 정의한다.
  • _calc() 메서드: 추상 메서드로, 하위 클래스에서 반드시 구현해야 한다.

GInstlycalc 클래스 (구상 클래스)

const GInstlycalc = class extends GPriceCalc {
    constructor(){}
    _calc() {
        // 실제 계산 부분
    }
};
  • GPriceCalc를 상속받아 _calc() 메서드를 구체적으로 구현한다.

GSpotcalc 클래스 (구상 클래스)

const GSpotcalc = class extends GPriceCalc {
    constructor(){}
    _calc() {
        // 실제 계산 부분
    }
};
  • GPriceCalc를 상속받아 _calc() 메서드를 다른 방식으로 구현한다.

사용예시

const calc = new GInstlycalc();
const result = calc.calc();

const calcSpot = new GSpotcalc();
const resultSpot = calcSpot.calc();

 두 가지 다른 계산 방식(GInstlycalc와 GSpotcalc)을 사용하고 있지만 둘 다 동일한 calc() 메서드를 호출한다.

내부적으로는 각각 다른 _calc() 구현을 사용하게 된다.


재정통화 

클래스 다이어그램

+-------------------+
|    FPriceCalc     |
+-------------------+
| - _makeKrw()      |
| - _makeUsd()      |
| + calc()          |
| # _calcJPYKRW()   |
| # _calcEURBGPAUDNZD() |
| # _calcETC()      |
| # _calc()         |
+-------------------+
         ^
         |
         |
+-------------------+     +-------------------+
|    FInstlycalc    |     |     FSpotcalc     |
+-------------------+     +-------------------+
| # _calcJPYKRW()   |     | # _calcJPYKRW()   |
| # _calcEURBGPAUDNZD()   | # _calcEURBGPAUDNZD()  |
| # _calcETC()      |     | # _calcETC()      |
+-------------------+     +-------------------+

이 다이어그램에서:

  1. FPriceCalc는 추상 기본 클래스이다:
    • calc()는 템플릿 메서드로, 알고리즘의 골격을 정의한다.
    • _makeKrw(), _makeUsd(), _calcJPYKRW(), _calcEURBGPAUDNZD(), _calcETC(), _calc()는 추상 메서드 또는 훅 메서드이다.
  2. FInstlycalc FSpotcalc는 구체 클래스이다:
    • 이들은 FPriceCalc를 상속받아 추상 메서드들을 구체적으로 구현한다.

템플릿 메서드 패턴의 흐름:

  1. 클라이언트가 구체 클래스(FInstlycalc 또는 FSpotcalc)의 인스턴스를 생성한다.
  2. 클라이언트가 calc() 메서드를 호출한다.
  3. calc() 메서드 내에서:
    • _makeKrw() _makeUsd()를 호출한다.
    • 상품 코드에 따라 _calcJPYKRW(), _calcEURBGPAUDNZD(), 또는 _calcETC()를 호출한다.
  4. 각 구체 클래스에서 구현된 메서드들이 실행된다.

전체소스

const FPriceCalc = class {
	constructor(){}
    _makeKrw() {}
    _makeUsd() {}
    calc(){
    	// 공통으로 처리
        this.priceKrw = _makeKrw();
        this.priceUsd = _makeUsd();
        
        if(this.prdcd == 'JPYKRW') this._calcJPYKRW();
        else if(
            this.prdcd == 'EURKRW' ||
            this.prdcd == 'GBPKRW' ||
            this.prdcd == 'AUDKRW' ||
            this.prdcd == 'NZDKRW') this._calcEURBGPAUDNZD();
        else this._calcETC();
        
    }
    _calc() {
    	throw 'override';
    }
};

// 바로환전
const FInstlycalc = class exends FPriceCalc {
	constructor() {}
    _calcJPYKRW() {}
    _calcEURBGPAUDNZD() {}
    _calcETC() {}
  
};

// 현물환
const FSpotcalc = class exends FPriceCalc {
	constructor(){}
    _calcJPYKRW() {}
    _calcEURBGPAUDNZD() {}
    _calcETC() {}
};

// 바로환전
const calc = new FInstlycalc();
const resultInst = calc.calc();

// 현물환
const calc = new FSpotcalc();
const resultInst = calc.calc();



const calc = new GInstlycalc();
const result = calc.calc();

const calcSpot = new GSpotcalc();
const resultSpot = calcSpot.calc();

FPriceCalc 클래스 (추상 기본 클래스)

const FPriceCalc = class {
    constructor(){}
    _makeKrw() {}
    _makeUsd() {}
    calc(){
        // 공통으로 처리
        this.priceKrw = this._makeKrw();
        this.priceUsd = this._makeUsd();
        
        if(this.prdcd == 'JPYKRW') this._calcJPYKRW();
        else if(
            this.prdcd == 'EURKRW' ||
            this.prdcd == 'GBPKRW' ||
            this.prdcd == 'AUDKRW' ||
            this.prdcd == 'NZDKRW') this._calcEURBGPAUDNZD();
        else this._calcETC();
    }
    _calc() {
        throw 'override';
    }
};
  • _makeKrw(), _makeUsd(): 원화와 달러 가격을 생성하는 메서드이다.
  • calc(): 템플릿 메서드로, 전체 계산 과정의 골격을 정의한다.
  • _calcJPYKRW(), _calcEURBGPAUDNZD(), _calcETC(): 상품코드에 따라 다른 계산 메서드를 호출한다.

FInstlycalc 클래스 (구상 클래스 - 바로환전)

const FInstlycalc = class extends FPriceCalc {
    constructor() {}
    _calcJPYKRW() {}
    _calcEURBGPAUDNZD() {}
    _calcETC() {}
};

FPriceCalc를 상속받아 각 계산 메서드를 구체적으로 구현한다.

FSpotcalc 클래스 (구체 클래스 - 현물환)

const FSpotcalc = class extends FPriceCalc {
    constructor(){}
    _calcJPYKRW() {}
    _calcEURBGPAUDNZD() {}
    _calcETC() {}
};

FPriceCalc를 상속받아 각 계산 메서드를 다른 방식으로 구현한다.

코드 동작 설명

  1. 먼저 FPriceCalc 클래스의 calc() 메서드가 호출된다.
  2. calc() 메서드 내에서:
    • _makeKrw()와 _makeUsd()를 호출하여 원화와 달러 가격을 생성한다.
    • this.prdcd(상품 코드)에 따라 적절한 계산 메서드를 호출한다:
      • 'JPYKRW'인 경우 _calcJPYKRW()
      • 'EURKRW', 'GBPKRW', 'AUDKRW', 'NZDKRW' 중 하나인 경우 _calcEURBGPAUDNZD()
      • 그 외의 경우 _calcETC()
  3. 각 구체 클래스(FInstlycalc, FSpotcalc)에서는 이러한 계산 메서드들을 자신의 방식대로 구현한다.

사용 예시

// 바로환전
const calc = new FInstlycalc();
const resultInst = calc.calc();

// 현물환
const calc = new FSpotcalc();
const resultInst = calc.calc();

바로환전과 현물환에 대해 각각 다른 계산 객체를 생성하고 calc() 메서드를 호출한다. 

내부적으로는 각 클래스의 구체적인 구현에 따라 계산이 이루어진다

 

템플릿 메소드 패턴의 결과

1. 코드 구조 개선

  • 코드의 구조를 명확히 하여 가독성과 유지보수성 향상
  • 공통 로직의 중복을 피하고 코드 재사용성 증가

2. 확장성 강화

  • 새로운 계산 방식을 쉽게 추가할 수 있어 시스템 확장이 용이
  • 상품별(바로환전/현물환/선물환/스왑), 통화별(일반통화/재정통화) 클래스로 격리되어 유연한 코드 수정 가능

3. 알고리즘 변형 관리

  • 유사하지만 세부적으로 다른 여러 알고리즘을 효과적으로 구현 및 관리
  • 각 변형에 대해 별도의 클래스를 만들어 관리하므로 알고리즘 간 간섭 최소화

4. 코드 일관성 유지

  • 기본 알고리즘 구조는 유지하면서 특정 단계만 변경 가능
  • 모든 변형 알고리즘이 동일한 기본 구조를 따르므로 일관성 확보

5. 유지보수 용이성

  • 각 상품 및 통화별로 클래스가 분리되어 있어 특정 부분의 수정이 다른 부분에 미치는 영향 최소화
  • 코드 변경 시 영향 범위를 쉽게 파악하고 관리 가능

6. 비즈니스 로직 분리

  • 공통 로직과 특정 알고리즘을 명확히 분리하여 비즈니스 로직의 가시성 향상
  • 새로운 비즈니스 요구사항 적용 시 해당 부분만 집중적으로 수정 가능

이러한 특성들로 인해 템플릿 메서드 패턴은 복잡한 금융 상품의 가격 계산과 같이 기본 구조는 유사하지만 세부 로직이 다양하게 변하는 시스템에 특히 유용하다.

템플릿 메소드 패턴을 통해 코드의 구조화, 재사용성, 확장성, 유지보수성을 크게 향상시킬 수 있었다.