[Design Pattern] 템플릿 메서드 패턴

olwooz·2023년 3월 4일
0

Design Pattern

목록 보기
21/22
알고리즘의 골격을 수퍼클레스에서 정의하지만 서브클래스가 구조를 바꾸지 않으면서 
알고리즘의 특정 단계를 override하는 행동 패턴

문제

기업 문서를 분석하는 데이터 마이닝 앱 가정

  • 앱에 다양한 포맷의 문서들을 넣어 동일한 포맷의 유의미한 데이터 추출
  • 앱의 첫 번째 버전 - DOC, 두 번째 버전 - CSV, 세 번째 버전 - PDF

  • 데이터 포맷을 다루는 코드는 다르지만 데이터 가공/분석 코드는 거의 동일 → 알고리즘 구조는 그대로 두고 코드 중복을 줄이고 싶음
  • 가공하려는 객체에 따라 수행 작업을 선택하는 조건문이 많음 → 공통 인터페이스나 기초 클래스를 통해 다형성으로 클라이언트 코드 조건문 제거하고 싶음

해결책

템플릿 메서드 패턴 - 알고리즘을 일련의 단계로 나눠 각 단계를 메서드로 바꾸고 단일 템플릿 메서드에서 이 메서드들에 일련의 호출을 보냄

  • 각 단계는 추상일 수도 있고 default 구현일 수도 있음
  • 알고리즘을 사용하려면 클라이언트는 자신의 서브클래스를 제공하고, 모든 추상 단계를 구현하고, 필요에 의해 선택적인 단계들을 override

위 예제

  • 3개의 파싱 알고리즘을 위한 기초 클래스 생성 → 다양한 문서 가공 단계들에 대한 일련의 호출으로 구성된 템플릿 메서드 정의
  • 처음엔 모든 단계들을 추상으로 선언해 서브클래스들이 해당 메서드들에 대한 구현을 스스로 제공하게 함
  • 중복을 줄이기 위해 raw 데이터 분석, 리포트 구성 등 비슷한 단계들은 기본 클래스에 넣어 서브클래스들이 코드를 공유하게 함
  • hooks (optional) - 알고리즘의 필수 단계들 전후로 위치해 서브클래스들에게 알고리즘의 추가적인 확장 포인트 제공

구조

1. 추상 클래스 - 알고리즘의 단계 역할을 하는 메서드들과, '
   이 메서드들을 특정 순서로 호출하는 실제 템플릿 메서드 선언
   - 각 단계들은 추상으로 선언되거나 default 구현을 가질 수 있음
   
2. concrete 클래스 - 템플릿 메서드 자체를 제외한 모든 단계를 override 할 수 있음

적용

클라이언트들이 전체 알고리즘이나 그 구조 말고 특정 단계만 확장할 수 있게 하려는 경우

- 모놀리식 알고리즘을 수퍼클래스에서 구조를 그대로 유지하면서 
  서브클래스들로 쉽게 확장될 수 있는 일련의 단계들로 나눔

사소한 차이들만 있고 거의 같은 알고리즘들을 가지는 여러 클래스들을 가지고 있어 알고리즘이 변경되면 모든 클래스를 변경해야 하는 경우

- 알고리즘을 템플릿 메서드로 바꾸면 비슷한 구현을 가지는 단계들을 
  수퍼클래스에 넣어 코드 중복을 없앨 수 있음

구현방법

1. 단계들로 나눌 수 있는 타겟 알고리즘 분석
   - 어떤 단계들이 모든 서브클래스들에 공통적이고 어떤 단계들이 항상 고유할 지 고려
   
2. 추상 기초 클래스를 생성하고 템플릿 메서드와 알고리즘의 단계들을 표현하는 
   추상 메서드들의 집합 선언
   - 템플릿 메서드에서 대응되는 단계들을 실행해 알고리즘 구조의 윤곽을 잡음
   - 템플릿 메서드를 final로 만들어 서브클래스들이 override 하지 못하게 하는 걸 고려
   
3. 모든 단계들이 추상이여도 괜찮지만 특정 단계들을 기본 구현을 가짐으로서 이득을 볼 수 있음
   - 서브클래스들은 이 메서드들을 구현하지 않아도 됨
   
4. 알고리즘의 필수적인 단계들 사이에 hooks를 추가하는 것을 고려

5. 알고리즘의 각 변형마다 새 concrete 서브클래스 생성
   - 모든 추상 단계들을 구현해야 하고, 선택적 단계들을 override 해도 됨

장단점

장점

- 클라이언트가 큰 알고리즘의 특정 부분들을 override 할 수 있게 해, 
  알고리즘의 다른 부분들에서 발생하는 변화에 영향을 덜 받게 할 수 있음
- 중복 코드를 수퍼클래스로 옮길 수 있음

단점

- 몇몇 클라이언트들은 주어진 알고리즘의 골격에 의해 제한될 수 있음
- 서브클래스를 통한 default 단계 구현을 제한해 LSP 위반
- 단계가 많을수록 템플릿 메서드 유지가 어려움

다른 패턴과의 관계

- 팩토리 메서드 - 템플릿 메서드의 특수화, 
  팩토리 메서드는 큰 템플릿 메서드의 한 단계 역할을 할 수 있음
  
- 템플릿 메서드 패턴 - 상속 기반, 알고리즘의 부분들을 서브클래스에서 확장해 변경, 
  클래스 레벨에서 동작해 정적임  
  전략 패턴 - 합성 기반, 객체 행동의 부분들을 다른 전략을 제공해줌으로써 변경, 
  객체 레벨에서 동작해 런타임에 행동들을 변경

TypeScript 예제

/**
 * The Abstract Class defines a template method that contains a skeleton of some
 * algorithm, composed of calls to (usually) abstract primitive operations.
 *
 * Concrete subclasses should implement these operations, but leave the template
 * method itself intact.
 */
abstract class AbstractClass {
    /**
     * The template method defines the skeleton of an algorithm.
     */
    public templateMethod(): void {
        this.baseOperation1();
        this.requiredOperations1();
        this.baseOperation2();
        this.hook1();
        this.requiredOperation2();
        this.baseOperation3();
        this.hook2();
    }

    /**
     * These operations already have implementations.
     */
    protected baseOperation1(): void {
        console.log('AbstractClass says: I am doing the bulk of the work');
    }

    protected baseOperation2(): void {
        console.log('AbstractClass says: But I let subclasses override some operations');
    }

    protected baseOperation3(): void {
        console.log('AbstractClass says: But I am doing the bulk of the work anyway');
    }

    /**
     * These operations have to be implemented in subclasses.
     */
    protected abstract requiredOperations1(): void;

    protected abstract requiredOperation2(): void;

    /**
     * These are "hooks." Subclasses may override them, but it's not mandatory
     * since the hooks already have default (but empty) implementation. Hooks
     * provide additional extension points in some crucial places of the
     * algorithm.
     */
    protected hook1(): void { }

    protected hook2(): void { }
}

/**
 * Concrete classes have to implement all abstract operations of the base class.
 * They can also override some operations with a default implementation.
 */
class ConcreteClass1 extends AbstractClass {
    protected requiredOperations1(): void {
        console.log('ConcreteClass1 says: Implemented Operation1');
    }

    protected requiredOperation2(): void {
        console.log('ConcreteClass1 says: Implemented Operation2');
    }
}

/**
 * Usually, concrete classes override only a fraction of base class' operations.
 */
class ConcreteClass2 extends AbstractClass {
    protected requiredOperations1(): void {
        console.log('ConcreteClass2 says: Implemented Operation1');
    }

    protected requiredOperation2(): void {
        console.log('ConcreteClass2 says: Implemented Operation2');
    }

    protected hook1(): void {
        console.log('ConcreteClass2 says: Overridden Hook1');
    }
}

/**
 * The client code calls the template method to execute the algorithm. Client
 * code does not have to know the concrete class of an object it works with, as
 * long as it works with objects through the interface of their base class.
 */
function clientCode(abstractClass: AbstractClass) {
    // ...
    abstractClass.templateMethod();
    // ...
}

console.log('Same client code can work with different subclasses:');
clientCode(new ConcreteClass1());
console.log('');

console.log('Same client code can work with different subclasses:');
clientCode(new ConcreteClass2());
// Output.txt

Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass1 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass1 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass2 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass2 says: Overridden Hook1
ConcreteClass2 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

참고 자료: Refactoring.guru

0개의 댓글