2024. 6. 19. 17:51ㆍ카테고리 없음
템플릿 메서드는 상위 클래스에서 알고리즘의 구조를 정의하고,
하위 클래스들이 알고리즘의 특정 단계들을 오버라이딩할 수 있도록 하는 디자인 패턴이다.
일단 예시를 보자
<커피를 만드는 레시피>와 <홍차를 만드는 레시피>가 있다.
커피를 만드는 레시피☕
1. 물을 끓인다.
2. 끓는 물에 커피를 우려낸다.
3. 커피를 컵에 따른다.
4. 설탕과 우유를 추가한다.
홍차를 만드는 레시피🍵
1. 물을 끓인다
2. 끓는 물에 찻잎을 우려낸다.
3. 홍차를 컵에 따른다.
4. 레몬을 추가한다.
한 눈에 봐도 두 레시피가 상당히 유사한 것을 알 수 있다.
코드를 통해 알아봅시다.
Coffee.java
public class Coffee {
void prepareRecipe() {
boilWater(); // 1. 물을 끓인다.
brewCoffeeGrinds(); // 2. 커피를 우려낸다.
pourInCup(); // 3. 커피를 컵에 따른다.
addSugarAndMilk(); // 4. 설탕과 우유를 추가한다.
}
public void boilWater() {
System.out.println("물 끓이는 중");
}
public void brewCoffeeGrinds() {
System.out.println("필터로 커피를 우려내는 중");
}
public void pourInCup() {
System.out.println("컵에 따르는 중");
}
public void addSugarAndMilk() {
System.out.println("설탕과 우유를 추가하는 중");
}
}
Tea.java
public class Tea {
void prepareRecipe() {
boilWater(); // 1. 물을 끓인다.
steepTeaBag(); // 2. 찻잎을 우려낸다.
pourInCup(); // 3. 홍차를 컵에 따른다.
addLemon(); // 4. 레몬을 추가한다.
}
public void boilWater() {
System.out.println("물 끓이는 중");
}
public void steepTeaBag() {
System.out.println("찻잎을 우려내는 중");
}
public void pourInCup() {
System.out.println("컵에 따르는 중");
}
public void addLemon() {
System.out.println("레몬을 추가하는 중");
}
}
커피와 홍차를 만드는 과정이 상당히 유사해 코드 중복이 많이 발생한다.
1번 물을 끓이는 과정과 3번 컵에 따르는 과정의 코드는 완벽히 일치하고,
2번과 4번 과정은 약간씩 다르지만, 비슷하다고 볼 수 있다.
2.
끓는 물에 커피를 우려낸다. --> brewCoffeeGrinds()
끓는 물에 찻잎을 우려낸다. --> steepTeaBag()
---> brew()라는 공통 메서드를 상위 클래스에 만들어서 사용할 수 있다.
4.
설탕과 우유를 추가 --> addSugarAndMilk()
레몬을 추가 --> addLemon()
---> addCondiments()라는 공통 메서드를 상위 클래스에 만들어서 사용할 수 있다.
이제 공통적인 부분을 찾아 일반화해서 상위 클래스를 만들어 봅시다
public abstract class CaffeineBeverage {
final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
abstract void brew();
abstract void addCondiments();
void boilWater() {
System.out.println("물 끓이는 중");
}
void pourInCup() {
System.out.println("컵에 따르는 중");
}
}
Coffee 클래스와 Tea 클래스에 있던 prepareRecipe() 라는 메서드를 템플릿(틀)으로 만들었고,
여기서는 카페인 음료를 만드는 알고리즘의 템플릿이 된다.
그리고 완전히 과정이 일치하는 boilWater()와 pourInCup()은 이 상위 클래스에서 구현하고 있다.
또한 음료마다 조금씩 달랐던 brew()와 addCondiments() 메서드는 하위 클래스에서 구현할 수 있도록 추상 메서드로 선언되었다.
---수정된 Coffee.java---
public class Coffee extends CaffeineBeverage {
@Override
public void brew() {
System.out.println("필터로 커피를 우려내는 중");
}
@Override
public void addCondiments() {
System.out.println("설탕과 우유를 추가하는 중");
}
}
---수정된 Tea.java---
public class Tea extends CaffeinBeverage {
@Override
public void brew() {
System.out.println("찻잎을 우려내는 중");
}
@Override
public void addCondiments() {
System.out.println("레몬을 추가하는 중");
}
}
brew()와 addCondiments()가 각각의 레시피에 맞게 오버라이딩이 되어있다.
이전 코드에서는 Coffee와 Tea가 각각 작업을 처리했어서 코드가 중복되고, 만약 레시피가 바뀐다면 일일이 음료 하나하나의 과정을 고쳐야 한다.
또한 새로운 음료가 추가된다면 또 그 레시피를 전부 구현해야 하기 때문에, 변경과 확장에 어려운 상태가 된다.
하지만? 템플릿 메서드를 적용한다면 상위클래스(CaffeineBeverage)에서 알고리즘을 구성하고 작업을 처리하기 때문에 알고리즘이 한 군데에 모여있게 되어 수정에 용이하고 코드를 재사용할 수 있다.!!!
또한 새로운 음료가 추가되어도 이미 레시피 틀이 만들어져있기 때문에 쉽게 추가할 수 있다.
이렇게 템플릿 메서드는 알고리즘의 골격(틀)을 정의하고, 서브클래스에서 일부 단계를 구현할 수 있으며,
알고리즘의 구조는 그대로 유지하면서 특정 단계는 서브 클래스에서 재정의할 수 있다.
AbstractClass에 알고리즘의 틀을 정의한 템플릿 메서드가 존재하고, 서브클래스가 알고리즘의 틀을 마음대로 수정하지 못하게 final로 선언할 수 있다.
primitiveOperation1()과 primitiveOperation2()는 추상메서드로, ConcreteClass에서 구체적인 구현을 한다.
또한 ConcreteClass는 여러 개가 있을 수 있다.
abstract class AbstractClass {
final void templateMethod() { // 템플릿 메서드는 각 단계를 메서드로 표현한다.
primitiveOperation1();
primitiveOperation2();
concreteOperation();
}
abstract void primitiveOperation1(); // 알고리즘의 이 단계는 서브클래스에서 구현하도록 한다.
abstract void primitiveOperation2(); // 알고리즘의 이 단계는 서브클래스에서 구현하도록 한다2.
void concreteOperation() {
// 구현 코드
}
}
템플릿 메서드 패턴 장점
- 상위 추상클래스로 로직을 공통화 하여 코드의 중복을 줄일 수 있다.
- 서브 클래스의 역할을 줄이고, 핵심 로직을 상위 클래스에서 관리하므로서 관리가 용이해진다
템플릿 메서드 패턴 단점
- 알고리즘의 제공된 골격에 의해 유연성이 제한될 수 있다.
- 알고리즘 구조가 복잡할수록 템플릿 로직 형태를 유지하기 어려워진다.
- 추상 메소드가 많아지면서 클래스의 생성, 관리가 어려워질 수 있다.