티스토리 뷰

1. 템플릿 메소드 패턴이란

템플릿 메소드는 알고리즘의 골격을 정의한다.
알고리즘의 여러 단계 중 일부는 서브클래스에서 구현할 수 있다.

템플릿 메소드를 이용하면 알고리즘의 구조는 그대로 유지하면서 서브클래스의 특정 단계를 재정의 할 수 있다.

 

구성요소

1) AbstractClass

- 추상화된 기본 함수들을 정의한다 (나중에 ConcreteClass가 구현할).

- 알고리즘의 뼈대를 정의하는 템플릿 메소드를 구현한다.
이 템플릿 메소드는 추상 메소드로 정의한 기본 함수들을 호출할 수 있으며 다른 내/외부 함수들을 호출할 수도 있다.

 

2) ConcreteClass

- 상속한 AbstractClass의 기본 함수들을 구체적으로 구현한다.

 

적용된 디자인원칙

일부 구체적인 로직은 하위 ConcreteClass에서 구체화하기 때문에 AbstractClass 입장에서는 Inversion of Control 이 일어났다고 볼 수 있다.

 

2. 동기

장점

1) 템플릿 코드를 재사용하고 중복 코드를 줄일 수 있다.

2) 템플릿 코드를 변경하지 않고 상속을 받아서 구체적인 알고리즘만 변경할 수 있다.

 

단점

1) 리스코프 치환 원칙을 위반할 수 있다.

2) 알고리즘 구조가 복잡할 수록 템플릿을 유지하기 어려워진다.

 

3. 구현

구현 시 고려사항

1) encapsulation 적용

- template method는 오버라이딩하면 안되므로 private으로 선언

- 오버라이딩 해야하는 기본 함수들은 protected로 선언

 

2) 기본 함수 수를 줄이기

- 오버라이딩 해야될 기본 단계 함수들을 줄일 수록 이 디자인 패턴의 의도를 잘 충족시킨 것이다.

abstract로 선언한 기본함수들이 많을수록 그 템플릿을 상속한 서브클래스들의 책임이 커지며 나중에 클라이언트가 해야될 일이 많아진다.

 

3) 네이밍 컨벤션

- 오버라이딩 되어야할 함수들은 이름 앞부분에 특정 어구를 붙여서 이후에 구현이 필요하다는 것을 알릴 수 있다.

예를 들면 "Do-" 를 붙일 수도 있다.

 

예시코드

다음과 같은 코드가 있다.

public 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 += Integer.parseInt(line);
            }
            return result;
        } catch (IOException e) {
            throw new IllegalArgumentException(path + "에 해당하는 파일이 없습니다.", e);
        }
    }
}

public class MultiplyFileProcessor {

    private String path;
    public MultiplyFileProcessor(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 *= Integer.parseInt(line);
            }
            return result;
        } catch (IOException e) {
            throw new IllegalArgumentException(path + "에 해당하는 파일이 없습니다.", e);
        }
    }
}

FileProcessor와 MultiplyFileProcessor에 중복코드가 보인다.

다른 부분은 process() 내 읽어들인 int를 곱하느냐 더하느냐의 차이만 있다.

 

이런 부분을 템플릿 메소드 패턴을 적용할 수 있다.

public abstract class FileProcessor {

    private String path;
    public FileProcessor(String path) {
        this.path = path;
    }

    public final 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 abstract int getResult(int result, int number);

}

public class Multiply extends FileProcessor {
    public Multiply(String path) {
        super(path);
    }

    @Override
    protected int getResult(int result, int number) {
        return result *= number;
    }
}

FileProcessor을 abstract로 선언하고, process를 템플릿 메소드로 만든 곱하기, 나누기를 담당하는 부분을 기본 단계 함수로 하여 abstract method로 만드는 것이다.

 

4. 템플릿 콜백 패턴, 그리고 스트래티지 패턴

템플릿 메소드 패턴을 보면 상속을 사용한다는 단점이 있다.

이를 대신해서 콜백으로 상속 대신 위임을 사용하는 템플릿 콜백 패턴이 있다.

콜백(callback)이란?
프로그래밍에서 콜백(callback) 또는 콜백함수(callback function)는 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 말한다. 콜백을 넘겨받는 코드는 이 콜백을 필요에 따라서 즉시 실행할 수도 있고, 아니면 나중에 실행할 수도 있다.

 

상속 대신 익명 내부클래스 또는 람다 표현식을 활용하는 것이다.

위 예시를 이어서 콜백 역할을 하는 인터페이스를 다음과 같이 선언한다.

public interface Operator {

    abstract int getResult(int result, int number);
}

이후 Multiply 는 사용하지 않아도 되고, Client에서 익명함수 또는 람다로 행동을 콜백함수로 구현한다.

public class Client {

    public static void main(String[] args) {
        FileProcessor fileProcessor = new Multiply("number.txt");
        int result = fileProcessor.process((sum, number) -> sum += number);
        System.out.println(result);
    }
}

 

이전에 배운 스트래티지 패턴과 매우 유사한 느낌이 든다. 어떤 차이점이 있는걸까?

5. 개인적인 포인트

- callback과 behavior parameterization의 차이는 뭘까?