티스토리 뷰
템플릿 메서드 패턴이란?
템플릿 메서드 패턴을 객체지향 디자인 패턴 중 하나로, 기능의 뼈대(템플릿)와 실제 구현을 분리하는 패턴입니다.
즉, 상위 클래스(추상 클래스)에서 알고리즘 골격을 정의하고 하위 클래스 (상속 받은 클래스)에서는 알고리즘의 특정 단계를 재정의할 수 있는 것입니다.
아래 [그림1] 은 템플릿 메서드를 보여주고자 하였고 왼쪽은 기본틀이며, 오른쪽은 제가 설명하기 위해 예시로 만든 틀입니다.
들어가기 전 단어 알기
훅 메서드 (hook method)
추상 클래스에서 추상 메서드로 선언하며, 기본 구현만 제공하는 메서드입니다.
이제 하위 (구현, 자식) 클래스에서 추상 메서드를 오버라이딩하여 사용할 수 있습니다.
템플릿 메서드 특징
템플릿 메서드 패턴은 다음과 같은 특징을 가지고 있습니다.
- 코드 중복을 줄일 수 있습니다.
- 상위 클래스에서 알고리즘 골격을 정의해서 사용하므로 알고리즘 관리가 쉽습니다.
- 추상 메서드 오버라이딩을 통해 하위(구현, 자식) 클래스 확장에 유리합니다.
- 상위 클래스에서 템플릿 메서드 순서를 보면 어떻게 동작하는지 한 눈에 보기 쉽습니다.
- 상위 클래스에서 구현, 기술을 많이 하게 되면 하위(구현, 자식) 클래스의 자유도가 줄어들며 결합도가 올라갑니다.
코드 예시
Spring MVC에 예시를 들면서 설명하려고 합니다.
현재 구조는 카드, 은행 계좌, 온라인 페이로 결제하면 주문이 생성되는 REST API를 호출했다고 가정하겠습니다.
코드는 길어서 더보기로 숨겨 놓았습니다.
1. 빈 껍대기 Entity 생성
- 각각 다른 클래스로 생성해 주었습니다.
// 주문 Entity
public class OrderEntity {
private PaymentEntity paymentEntity;
private UserEntity userEntity;
public OrderEntity() {}
public OrderEntity(PaymentEntity paymentEntity, UserEntity userEntity) {
this.paymentEntity = paymentEntity;
this.userEntity = userEntity;
}
}
// 유저 Entity
public class UserEntity { }
// 결제 Entity
public class PaymentEntity { }
2. PaymentService 생성 (추상클래스 생성)
- 여기서 authenticate(), processPayment() 메서드는 위에서 말한 hook method 입니다.
import hello.spring.transaction.pay.OrderEntity;
import hello.spring.transaction.pay.PaymentEntity;
import hello.spring.transaction.pay.UserEntity;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class PaymentService {
// 총 프로세스
public void pay(long amount){
UserEntity user = authenticate();
PaymentEntity payment = processPayment(amount);
createOrder(payment, user);
}
// 인증 메서드
protected abstract UserEntity authenticate();
// 결제 처리 메서드
protected abstract PaymentEntity processPayment(long amount);
// 결제 후 결제 정보로 주문 생성
public void createOrder(PaymentEntity paymentEntity, UserEntity userEntity) {
OrderEntity createOrder = new OrderEntity(paymentEntity, userEntity);
// createOrder 저장
log.info("결제 정보와 유저 정보로 Oder 생성하여 저장");
// 추가 로직 실행
}
}
3. Card, BankAccount, OnlinePay 생성 (구현 클래스 생성)
- 각각 다른 클래스로 생성해 주었습니다.
// 카드 구현 클래스1
@Slf4j
@Service
@Qualifier("cardPaymentService")
public class CardPaymentService extends PaymentService {
@Override
protected UserEntity authenticate() {
log.info("Card User 인증");
return new UserEntity();
}
@Override
protected PaymentEntity processPayment(long amount) {
log.info("Card 결제 프로세스 진행");
return new PaymentEntity();
}
}
// 은행 계좌 구현 클래스2
@Slf4j
@Service
@Qualifier("bankAccountPaymentService")
public class BankAccountPaymentService extends PaymentService{
@Override
protected UserEntity authenticate() {
log.info("BankAccount User 인증");
return new UserEntity();
}
@Override
protected PaymentEntity processPayment(long amount) {
log.info("BankAccount 결제 프로세스 진행");
return new PaymentEntity();
}
}
// 온라인 계좌 구현 클래스3
@Slf4j
@Service
@Qualifier("onlinePaymentService")
public class OnlinePaymentService extends PaymentService{
@Override
protected UserEntity authenticate() {
log.info("OnlinePay User 인증");
return new UserEntity();
}
@Override
protected PaymentEntity processPayment(long amount) {
log.info("OnlinePay 결제 프로세스 진행");
return new PaymentEntity();
}
}
4. Controller 생성 (호출하기 위해)
import hello.spring.transaction.pay.service.PaymentService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/payments")
@RequiredArgsConstructor
public class PaymentController {
@Qualifier("cardPaymentService")
private final PaymentService cardPaymentService;
@Qualifier("bankAccountPaymentService")
private final PaymentService bankAccountPaymentService;
@Qualifier("onlinePaymentService")
private final PaymentService onlinePaymentService;
@GetMapping("/card")
public String cardPayment(@RequestParam(name = "amount") long amount){
cardPaymentService.pay(amount);
return "ok Card";
}
@GetMapping("/bank-account")
public String bankAccountPayment(@RequestParam(name = "amount") long amount){
bankAccountPaymentService.pay(amount);
return "ok Bank-Account";
}
@GetMapping("/online")
public String onlinePayment(@RequestParam(name = "amount") long amount){
onlinePaymentService.pay(amount);
return "ok Online";
}
}
5. 기동하여 API 호출
서버를 시작하고 http://localhost:8080/payments/card?amount=10000를 URL로 적고 호출하게 되면
[그림2] 같은 화면이 나오게 됩니다.
이제 Spring Console창을 보면 [그림3] 처럼 나오는 것을 확인 할 수 있습니다.
코드 작동과 템플릿 메서드 작동 이미지 트레이닝
위에 있는 코드를 그림으로 본다면 [그림4]와 같습니다.
hook method인 authenticate()와 processPayment()는 자식 클래스에서 구현하고 있었으며
createOrder는 추상클래스에서 기본 알고리즘을 구현해 있엇습니다.
템플릿 메서드 단점
- 템플릿 메서드패턴은 상속을 사용하기 때문에 자식, 부모 클래스가 컴파일 시점에 강하게 결합됩니다.
- 결합도가 올라가면 유지보수, 수정할 때 부모, 자식 다 수정해야하는 번거로운 일을 이 생깁니다.
즉, 잘못된 의존 관계 때문에 부모 클래스를 수정하면, 자식 클래스에도 영향을 줄 수 있습니다. - 자식 클래스 입장에서는 부모클래스 기능을 사용하지 않아도 부모클래스를 의존하해야 합니다.
이것은 좋은 설계가 아닙니다.
결론
중복 코드가 발생하였을 때 사용하여 코드 중복제거를하는 아주 좋은 디자인 패턴이지만,
추상 클래스에서 많이 구현, 기술을 하게 된다면 결합도가 올라가게 되니 주위 하도록하고,
이 단점을 극복한 디자인패턴 중 전략 패턴도 있으니 다음에 알아보도록 하겠습니다.

감사합니다.
'백엔드 > 🎃세부 설계' 카테고리의 다른 글
[디자인패턴] Proxy 프록시 패턴 : 코드 보안과 성능 향상을 위한 패턴 (0) | 2024.05.30 |
---|---|
[디자인패턴] Strategy 전략 패턴 : 효율적인 코드 재사용을 위한 전략 패턴 (0) | 2024.05.23 |
[Process] 더보기 : 컨텐츠 더 보기 기능을 구현할 때 고려해야할 것 (0) | 2024.03.14 |
[디자인패턴] Singleton 싱글톤 : 프로그래밍 세계의 유일무이한 인스턴스 (0) | 2024.02.21 |
[Process] DragAndDrop : 그리드간 드래그기능 구현할 때 고려해야할 것 (0) | 2024.02.01 |
- Total
- Today
- Yesterday
- aws
- Front
- 네트워크
- 개발자
- 디자인패턴
- jvm
- Mac
- spring
- Cors
- AJAX
- 비동기
- 데이터 베이스
- Fetch
- java
- 프론트
- 오라클
- 깃허브 액션
- 프로세스
- 개발
- 템플릿
- 코딩테스트
- 개발블로그
- 개발환경
- DBeaver
- 자바스크립트
- Spring Security
- git
- JavaScript
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |