추상 클래스와 인터페이스에 대해 공부했을 때, 다음과 같은 추상 클래스의 문법적 특징이 있었다.
추상 클래스의 메서드는 아직 구현되지 않은 추상 클래스의 다른 메서드(추상 메서드)를 호출할 수 있다
이것은 추상 메서드가 제공하는 강력한 추상화 문법으로, 공통된 로직은 추상 클래스에 미리 정의해두고, 각 구현 클래스에서 달라져야 하는 부분만 추상 메서드를 오버라이딩하여 작성할 수 있다 (템플릿 메서드 패턴)
템플릿 메서드 패턴이 무엇일까?
템플릿 메소드 패턴은 기능의 뼈대(템플릿)와 실제 구현을 분리하는 패턴이다.
특정 작업을 처리하는 부분을 서브 클래스로 캡슐화하여, 전체적인 구조는 바꾸지 않으면서 특정 단계에서 수행하는 내용을 바꿀 수 있다.
먼저, 상위 클래스에서 뼈대 역할에 해당하는 템플릿 메서드를 정의한다. 이후, 템플릿 메서드에 포함된 메서드들의 구체적인 구현은 하위 클래스가 담당하게 된다.
템플릿 메서드 패턴에서 hook 메서드는 다음과 같은 특징을 가진다.
커피와 차를 만드는 과정은 물을 끓여 각각 알맞은 재료를 넣는다는 점에서 기본적인 알고리즘이 유사하다. 따라서 다음과 같이 추상 클래스에서 템플릿 메서드를 통해 기본적인 알고리즘을 정의할 수 있다.
public abstract class Beverage {
// 템플릿 메서드
public void prepareBeverage() {
boilWater();
brew();
pourInCup();
if (customerWantsCondiments()) {
addCondiments();
}
}
// 추상 메서드
public abstract void brew();
public abstract void addCondiments();
// 기본 메서드
void boilWater() {
System.out.println("Boiling water");
}
void pourInCup() {
System.out.println("Pouring into cup");
}
// hook 메서드
boolean customerWantsCondiments() {
return true;
}
}
이후 커피, 차에 알맞은 구체적인 구현은 서브 클래스에서 이루어진다.
// Concrete 클래스
class Coffee extends Beverage {
void brew() {
System.out.println("Dripping Coffee through filter");
}
void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
// hook 메서드 오버라이딩
boolean customerWantsCondiments() {
return false;
}
}
// Concrete 클래스
class Tea extends Beverage {
void brew() {
System.out.println("Steeping the tea");
}
void addCondiments() {
System.out.println("Adding Lemon");
}
}
중복을 줄이고, 코드의 재사용성을 확보할 수 있다
핵심 알고리즘의 관리가 쉽다
추상 메서드 재정의를 통한 서브 클래스 확장에 유리하다
상위 클래스의 메서드만 보더라도 전체 동작을 이해하기 쉽다
상위 클래스에서 선언된 메서드를 하위 클래스에 구현할 때 그 메서드가 어느 타이밍에 호출되는지 이해하고 있어야 한다
상위 클래스에서 기술을 많이 하면 하위 클래스의 자유도가 줄어든다
반대로 상위 클래스에서 기술을 적게 하면 하위 클래스에서 모두 재정의해야 하기 때문에 복잡도가 증가하고, 코드의 중복이 생길 수 있다
참고 : https://engineering.linecorp.com/ko/blog/templete-method-pattern, https://coding-factory.tistory.com/712