템플릿 메서드 패턴 / 템플릿 콜백 패턴

템플릿 메서드 패턴이란?

코드에 변하는 부분 변하지 않는 부분이 있을 때, 변하지 않는 부분을 추상 클래스 내에 메서드로 정의하고, 변하는 부분은 추상 클래스 내에 abstract 메서드로 정의하여 자식 클래스에서 변하는 부분을 abstract 메서드를 오버라이드해서 구현하는 패턴이다.

단점

  • 템플릿 메서드 패턴의 특징은 구현 클래스가 추상 클래스와 강하게 결합되어 있다.(상속)

    → 부모 클래스가 변경될 때 자식 클래스도 바로 영향을 받게 된다.

예를 들어서, 두 수와 연산을 입력 받아 결과를 출력하는 함수를 추상클래스로 두고, 더하기 빼기를 구현하면

@Slf4j
public abstract class AbstractCalculate {

    public void calc(int a, int b, String cmd) {
        log.info("{} {} {}", a, cmd, b);
        int answer = calculate(a, cmd, b);
        log.info("결과는 {}입니다.", answer);
    }

    protected abstract int calculate(int a, String cmd, int b);
}

더하기

public class PlusCalculate extends AbstractCalculate{

    @Override
    protected int calculate(int a, String cmd, int b) {
        if (cmd.equals("+")) {
            return a + b;
        }
        throw new IllegalArgumentException("더하기가 아닙니다!");
    }
}

빼기

public class MinusCalculate extends AbstractCalculate{
    @Override
    protected int calculate(int a, String cmd, int b) {
        if (cmd.equals("-")) {
            return a - b;
        }
        throw new IllegalArgumentException("빼기가 아닙니다!");
    }
}
스크린샷 2023-10-09 오후 5 58 55

템플릿 콜백 패턴이란?

런타임에서 익명 클래스를 이용해 동적으로 전략 알고리즘을 주입한다.

여기서 콜백은 익명 클래스(람다)를 만들어진 메서드를 칭하는 것이다.

특징으로는 전략 패턴과 DI의 장점을 익명 내부 클래스 사용 전략과 결합해 독특하게 활용된다.

정리하면 복잡하지만 바뀌지 않는 일정한 패턴을 갖는 작업 흐름이 존재하고, 그 중 일부분만 자주 바꿔서 사용해야 하는 경우에 적합한 패턴이다.

콜백

여기서 콜백은 하나의 오브젝트를 다른 오브젝트의 메서드에 매개변수로 넘겨주는 실행 가능한 코드를 말한다.

파라미터로 전달되지만 특정 로직을 담은 일종의 함수를 넘겨서 실행 시키기 위해 사용된다.

Untitled
public interface Strategy {//콜백

    public abstract void ChoosePen();
}

public class Student {//템플릿

    public void takeNotes(Strategy strategy) {
        System.out.println("=== 선생님이 펜을 주십니다. ===");
        strategy.ChoosePen();
        System.out.println("필기를 시작합니다.");
    }
}

public class Teacher {
    public static void main(String[] args) {
        Student student = new Student();

        student.takeNotes(new Strategy() {
            public void ChoosePen() {
                System.out.println("검은펜을 잡았습니다.");
            }
        });

        student.takeNotes(new Strategy() {
            public void ChoosePen() {
                System.out.println("빨간펜을 잡았습니다.");
            }
        });

        student.takeNotes(new Strategy() {
            public void ChoosePen() {
                System.out.println("파란펜을 잡았습니다.");
            }
        });
    }
}

클라이언트 단에서 익명 클래스를 통해 전략을 그때 그때 정해주면서 로직을 수행할 수 있다.

매번 익명 클래스를 만들면 번거로우니 Student에 넣으면

//Client가 준 전략을 수행하는 Context 클래스
public class Student {
    public void takeNotes(String Pen) {
        System.out.println("=== 선생님께서 펜을 주십니다. ===");
        takePen(Pen).ChoosePen();
        System.out.println("필기를 시작합니다.");
    }

    private Strategy takePen(String Pen) {
        return new Strategy() {
            @Override
            public void ChoosePen() {
                System.out.println(Pen + "을 잡았습니다.");
            }
        };
    }
}

public class Teacher {
    public static void main(String[] args) {
				//Context 객체 생성
        Student student = new Student();

        student.takeNotes("검정펜");
        student.takeNotes("빨간펜");
        student.takeNotes("파란펜");
        student.takeNotes("무지개 형관펜");
    }
}

이렇게 익명 클래스를 Student 객체 내부로 옮기고 변하는 최소한의 부분만 클라이언트에 주입 하도록하면 코드가 간결해진다.

스크린샷 2023-10-09 오후 6 13 28

장단점

장점

  1. 전략 패턴은 따로 전략 알고리즘을 정해 놓은 별도의 전략 클래스가 필요 했지만, 전략을 사용하는 메서드에 메개변수값으로 전력 로직을 넘겨 실행하기 때문에 전략 객체를 하나 하나 만들 필요가 없다.

  2. 외부에서 어떤 전략을 사용하는지 감추고 중요한 부분에 집중할 수 있다.

단점

  1. DI를 안쓰면 Bean 등록을 못해 싱글톤 객체가 안된다.

  2. 인터페이스를 사용하지만 직접 사용할 클래스를 직접 선언하기에 결함도가 증가한다. 하지만 무리하게 낮추는 건 필요없다.

코드

  • Context가 템플릿 역할

  • Strategy 부분이 콜백

참고 자료

Last updated