Blog
강의 리뷰
백기선 GoF 디자인 패턴
Behavioral Patterns
Template Method

템플릿 메서드 패턴 (Template Method)

알고리즘의 구조를 템플릿으로 제공하고 구체적으로 입력, 출력 등의 구현은 서브클래스에서 구현하도록 하는 패턴이다.
추상 클래스는 템플릿을 제공하고 하위 클래스는 구체적인 알고리듬을 제공한다.

적용 예시

아래와 같은 코드가 있다고 가정할 때 result += Integer.parseInt(line) 부분의 요구사항이 변경되어서 곱셈으로 변경되야 한다고 가정해보자. 그러면 아래의 클래스를 복사한 후 result += Integer.parseInt(line) 부분만 변경한 중복이 굉장히 많이 발생하는 코드가 발생할 수 있다.

public class FileProcessor {
    private String path;
 
    public FileProcessor(String path) {
        this.path = path;
    }
 
    public int process() {
        // try문이 끝날 때 BufferedReader가 자동으로 close된다.
        // Python의 with open() as f: 와 같은 기능을 한다.
        try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
            int result = 0;
            String line = null;
            while ((line = reader.readLine()) != null) {
                /**
                 * parseInt는 int type을 반환한다.
                 * valueOf는 Integer type을 반환한다.
                 */
                result += Integer.parseInt(line);
                // result += Integer.valueOf(line);
            }
            return result;
        } catch (IOException e) {
            throw new IllegalArgumentException(path + "에 해당하는 파일이 없습니다.", e);
        }
    }
}

위와 같은 문제를 해결하기 위해서 FileProcessor 부분을 Abstract class로 변경하고 입력과 출력 부분만 구현한채 result += Integer.parseInt(line) 부분에 해당하는 세부 구현만 자식 클래스에서 구현하도록 다음과 같이 변경할 수 있다.

public abstract class FileProcessor {
    private String path;
 
    public FileProcessor(String path) {
        this.path = path;
    }
 
    public int process() {
        try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
            int result = 0;
            String line = null;
            while ((line = reader.readLine()) != null) {
                result = getResult(result, Integer.parseInt(line));
            }
            return result;
        } catch (IOException e) {
            throw new IllegalArgumentException(path + "에 해당하는 파일이 없습니다.", e);
        }
    }
 
    // protected로 구현클래스에서 접근 가능하게 설정
    protected abstract int getResult(int result, int line);
}

Template Callback

  1. 구체적인 알고리즘(구현) 부분에 넣을 Interface 정의
  2. 템플릿에 위에서 정의한 Interface를 주입받아서 Callback을 실행하는 메소드를 정의
  3. Client에서 Interface의 구현체 주입
public interface Operator {
    abstract int getResult(int result, int number);
}

장점과 단점

  • 장점
    • 템플릿 코드를 재사용하고 중복 코드를 줄일 수 있다.
    • 템플릿 코드를 변경하지 않고 상속을 받아서 구체적인 알고리즘만 변경할 수 있다.
  • 단점
    • 리스코프 치환 원칙을 위반할 수 있다.
      • 템플릿에 해당하는 메서드, 위에서 예를 들면 process 부분이 재정의되서 이상한 동작을 할 수 있을 가능성이 있는데, 이는 process 부분을 final로 정의해서 재정의를 막을 수도 있다.
    • 알고리즘 구조가 복잡할 수록 템플릿을 유지하기 어려워진다.

Spring에서 사용하는 예시

  • HttpServlet
    • HttpServlet을 상속받아서 쓸 때 doGet, doPost, service 등의 메서드를 필요한 부분만 구현해서 쓰도록 한다.
  • ConfigurerAdapter
  • JdbcTemplate
    • excute라는 메서드는 데이터를 삽입할 때, query라는 메서드는 수정할 때 사용한다.
    • 두 메서드 다 callback이 필요한데 여기서 템플릿 콜백 패턴이 사용되었다.
  • RestTemplate