2022. 1. 1. 00:38ㆍETC/Design Patterns
Behavioral Class Pattern
Template Method Pattern
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.
- GoF Design Patterns
템플릿 메서드 패턴은 알고리즘의 구조를 메소드에 정의하고, 하위 클래스에서 알고리즘 구조의 변경없이 알고리즘을 재정의 하는 패턴입니다. 메서드를 이용해 각 단계를 템플릿 구조화하고 행동을 구분합니다.
템플릿Template에는 ‘형판’, ‘견본’이라는 뜻이 있습니다.
객체지향에서의 템플릿은 공통된 로직을 단계적으로 처리하는 것을 뜻합니다.
템플릿 메서드는 공통된 로직을 분리하여 캡슐화하는데, 공통 단계 템플릿을 별도의 메서드로 작성합니다.
때문에 템플릿 메서드 패턴은 로직의 전체 구조를 변경하지 않고 일부분만 수정할 때 유용합니다.
큰 틀에서 보면 비슷한 로직이지만, 미세한 차이로 인해 코드를 중복 작성하는 경우가 많습니다.
중복된 코드는 유지 보수를 어렵게 만드는 원인이 됩니다.
이 때, 추상화를 통해 중복된 코드를 제거할 수도 있고, 하위 클래스로 다양한 동작을 분리할 수도 있습니다.
또한 필요에 따라 처리하는 동작을 그룹별로 생성할 수 있습니다.
실세계 예제
Head First Design Patterns, 2nd edition 에서 소개해주는 예제를 같이 살펴보도록 할게요.
전체 코드는 Github를 참고해주세요!
커피와 차, 이 두 음료를 만드는 과정은 아주 비슷합니다.
만드는 과정을 아래와 같이 표현할 수 있습니다.
✔️ 커피 만들기
1. 물 끓이기
2. 끓인 물로 커피 내리기
3. 커피를 컵에 따르기
4. 설탕이나 우유 추가하기
✔️ 차 만들기
1. 물 끓이기
2. 끓인 물로 차 우려내기
3. 차를 컵에 따르기
4. 레몬 추가하기
이걸 코드로 짜면 어떨까요?
// 커피 만들기
void recipe() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
// 차 만들기
void recipe() {
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}
어떤가요? 둘의 코드를 보니 동일한 과정에 동일한 로직을 요구하고 있는 것이 보이시나요?
이것을 한 객체 내부에 따로 구현하면 코드가 중복되고 관리하기도 어려워지겠죠.
그럼 이제 Template Method를 적용하여 공통된 단계로 표현해볼게요.
void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
brewCoffeeGrinds()
메소드와 steepTeaBag()
메소드를 brew()
로 표현하고,
addSugarAndMilk()
메소드와 addLemon()
메소드를 addCondiments()
표현하여 공통의 로직으로 표현할 수 있습니다.
Template Method는 추상 클래스를 취합니다.
그림에서 너무 잘표현해준 것 같아 가져왔는데요. 크게 두 가지로 내용을 정리할 수 있어요.
첫 번째는, prepareRecipe()
가 음료를 제작하는 과정에 대한 알고리즘의 템플릿을 제공해주는 메서드이기 때문에 템플릿 메서드라는 것을 명시해주고 있어요.
두 번째는, 템플릿 메서드 내부의 메서드들 중 몇 가지(boilWater
, pourInCup
)는 해당 클래스(CaffeineBeverage)에서 처리하고,
몇 가지(brew
, addCondiments
)는 클래스의 서브 클래스에서 처리한다는 것을 명시해주고 있습니다.
템플릿 메서드에 대한 이해가 선명해지셨나요?
Template Method 구조
템플릿 메서드 패턴은 메서드를 정의하는 추상클래스와 템플릿 구현부인 일반 클래스로 구성되어 있습니다.
상위 클래스인 추상 클래스는 외형적인 뼈대만 결정하며, 하위 클래스를 구현하는 기준이 됩니다.
그래서 상위 클래스를 구성할 때 공통적 기능만 구현하고, 변경되는 부분은 추상 메서드로 선언합니다.
실제 수행은 하위 클래스에서 구현하여 처리하죠.
이렇게 공통된 코드를 상위 클래스로 옮기기 때문에 코드를 효율적으로 유지 보수할 수 있다는 장점을 가집니다.
템플릿 메서드 패턴을 설계할 때 중요한 부분은 상위 클래스와 하위 클래스의 역할을 분배하는 것입니다.
객체지향의 상속을 구현하기 위해 공통된 부분과 다른 부분을 분리하는 과정을 '일반화'라고 합니다.
Hook Class
하위 클래스에서 구현되는 메서드를 후크Hook 메서드라고도 하는데요, 추상 클래스를 통해 호출 방식을 미리 정의하고 하위 클래스에서 실체를 구현하는 것이 후크 기능과 유사하기 때문입니다.
후크는 중복된 코드를 제거하고 처리 로직의 일부를 변경할 때 자주 사용하는 기법인데요. 상속으로 후크를 구현할 때는 오버라이드를 사용하며, 오바라이드는 기존의 메서드를 남겨둔 채 중복된 메서드를 새로 추가합니다.
추상 클래스로 후크를 처리할 때는 재정의가 아닌 미정의된 메서드를 신규로 구현합니다.
Hollywood Principle
Don't call us, we'll call you
템플릿 메서드는 할리우드 원칙hollywood principle이라는 역전 제어 구조를 사용합니다.
배우가 영화에 출연하기 위해 자신의 프로필을 영화사에 전달한다.
그리곤 캐스팅 상태를 지속적으로 물어보는데, 영화사 입장에서는 매일 연락하는 배우들에게 캐스트이 상태를 통보해주기 어렵다.
그래서 영화사는 모든 배우에게 “앞으로 연락하지 말고 캐스팅되면 저희가 연락 드리겠습니다”라고 공지하고, 배우는 캐스팅 상태를 영화사에 물어보지 않은 채 통보를 기다리는 방식을 채택한다.
다른 비유로는 입사시험을 치른 후 합격통지를 기다리는 취업 준비생을 들 수 있다.
저수준 구성요소에서 시스템에 접속을 할수는 있지만, 언제 어떤식으로 그 구성요소들을 사용할지는 고수준 구성요소에서 결정한다는 것이 할리우드 원칙인데요. 템플릿 메서드 또한 저수준 구성요소(하위 클래스)를 고수준 구성요소(상위 클래스)에서 결정하기 때문에 할리우드 원칙을 적용한 사례입니다.
아래는 음료 만드는 예제로 할리우드 원칙을 쉽게 확인해보고자 첨부하는 구조도입니다.
'Final' Keyword
템플릿 메서드는 기능의 구조를 템플릿에 미리 정의합니다.
템플릿은 공통된 기능으로 하위 클래스에서 오버라이딩되지 않도록 방지해야합니다.
상속 구조에서 final 키워드를 사용하면 메서드의 오버라이딩을 방지할 수 있습니다.
그럼, 실제 코드들을 살펴보며 적용된 모습을 확인해보도록 할게요.
Sample Code: Java
TemplateMethod
public abstract class CaffeineBeverage {
final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
abstract void brew();
abstract void addCondiments();
void boilWater() {
System.out.println("Boiling Water");
}
void pourInCup() {
System.out.println("Pouring into Cup");
}
}
Concrete Class
public class Coffee extends CaffeineBeverage {
public void brew() {
System.out.println("Dripping Coffee through filter");
}
public void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
}
public class Tea extends CaffeineBeverage{
public void brew() {
System.out.println("Steeping the Tea");
}
public void addCondiments() {
System.out.println("Adding Lemon");
}
}
Main
public class Main {
public static void main(String[] args) {
Tea tea = new Tea();
Coffee coffee = new Coffee();
System.out.println("-- Make a Tea --");
tea.prepareRecipe();
System.out.println("\n-- Make a coffee --");
coffee.prepareRecipe();
}
}
출력
-- Make a Tea --
Boiling Water
Steeping the Tea
Pouring into Cup
Adding Lemon
-- Make a coffee --
Boiling Water
Dripping Coffee through filter
Pouring into Cup
Adding Sugar and Milk
추가로, Arrays.sort() 도 변형된 템플릿 메소드 패턴입니다.
예제 코드는 Github 코드를 참고하세요!
관련 패턴
C- Creational Patterns | S - Structural Patterns | B - Behavioral Patterns
C: Factory Method
팩토리 메서드 패턴은 추상화를 통해 객체의 요청과 생성을 분리합니다.
요청과 생성을 분리할 때는 템플릿 메서드 패턴을 응용합니다.
B: Strategy
템플릿 메서드는 상위 클래스에서 먼저 큰 골격에 대한 동작의 흐름을 구현하고, 그 외의 실제적인 동작은 하위 클래스에서 구현합니다. 전략 패턴 또한 위임을 통해 알고리즘 동작을 변경하는데, 템플릿 메서드 패턴이 전략 패턴에서 구현하는 알고리즘과 유사하게 볼 수 있습니다.
둘의 차이점은 템플릿 메서드 패턴은 알고리즘의 동작 일부를 변경하지만 전략 패턴은 알고리즘 동작 전체를 변경한다는 것입니다.
그럼 지금까지 Template Method Pattern에 대해 알아보았습니다.
오타나 잘못된 내용이 있다면 댓글로 남겨주세요!
감사합니다 ☺️
세상에 포스팅하다 새해가 되어버렸네요 🤦🏻♀️
새해 복 많이 받으세요 😉
'ETC > Design Patterns' 카테고리의 다른 글
Design Pattern, Abstract Factory (0) | 2022.01.05 |
---|---|
Design Pattern, Factory Method (0) | 2022.01.03 |
Design Pattern, Strategy (0) | 2021.12.29 |
Design Pattern, Builder (1) | 2021.12.29 |
Design Pattern, Facade (0) | 2021.12.26 |
Backend Software Engineer
𝐒𝐮𝐧 · 𝙂𝙮𝙚𝙤𝙣𝙜𝙨𝙪𝙣 𝙋𝙖𝙧𝙠