Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- UXUI기초정복
- mysql
- 백엔드
- Java
- 티스토리챌린지
- 부트캠프
- baekjoon
- 내일배움카드
- 국비지원
- 디자인교육
- 백엔드 부트캠프
- 백엔드개발자
- 객체지향
- 패스트캠퍼스
- 국비지원교육
- 디자인강의
- Be
- 국비지원취업
- UXUIPrimary
- OPENPATH
- 백준
- KDT
- UXUI챌린지
- 오픈패스
- 내일배움캠프
- Spring
- 환급챌린지
- 디자인챌린지
- 오픈챌린지
- 오블완
Archives
- Today
- Total
군만두의 IT 공부 일지
[스터디6] 06. 스프링이 사랑한 디자인 패턴 - 쇼핑몰 서비스 본문
목차
이번에는 다양한 디자인 패턴 중 일부를 실제 프로젝트에 적용해 보기로 했다. 각 패턴의 기본 개념과 실습을 진행하려고 한다. 이전 게시글에서 설계한 쇼핑몰 서비스에 대해 디자인 패턴(어댑터, 전략, 템플릿 콜백)을 적용할 것이다.
06. 스프링이 사랑한 디자인 패턴
- 디자인 패턴:
- 실제 개발 현장에서 비즈니스 요구 사항을 프로그래밍으로 처리하면서 만들어진 다양한 해결책 중에서 많은 사람들이 인정한 베스트 프랙티스를 정리한 것
- 객체 지향의 특성 중 상속(extends), 인터페이스(interface/implements), 합성(객체를 속성으로 사용)만을 이용한다.
- 스프링 프레임워크: 자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량급 애플리케이션 프레임워크
1. 어댑터 패턴(Adapter Pattern)
- 어댑터 패턴:
- 어댑터: 변환기(converter)
- 객체를 속성으로 만들어서 참조하는 디자인 패턴
- "호출당하는 쪽의 메서드를 호출하는 쪽의 코드에 대응하도록 중간에 변환기를 통해 호출하는 패턴"
- 예) ODBC, JDBC, JRE 등
2. 프록시 패턴(Proxy Pattern)
- 프록시 패턴:
- 프록시: 대리자, 대변인
- 실제 서비스 메서드의 반환값에 가감하는 것을 목적으로 하지 않고 제어의 흐름을 변경하거나 다른 로직을 수행하기 위해 사용한다.
- "제어 흐름을 조정하기 위한 목적으로 중간에 대리자를 두는 패턴"
- 개방 폐쇄 원칙(OCP)와 의존 역전 원칙(DIP) 적용
- 대리자는 실제 서비스와 같은 이름의 메서드를 구현한다. 이때 인터페이스를 사용한다.
- 대리자는 실제 서비스에 대한 참조 변수를 갖는다(합성).
- 대리자는 실제 서비스의 같은 이름을 가진 메서드를 호출하고 그 값을 클라이언트에게 돌려준다.
- 대리자는 실제 서비스의 메서드 호출 전후에 별도의 로직을 수행할 수도 있다.
3. 데코레이터 패턴(Decorator Pattern)
- 데코레이터 패턴:
- 데코레이터: 도장/도배업자, 장식자
- 실제 서비스의 반환 값을 예쁘게 포장(장식)하는 패턴
- "메서드 호출의 반환값에 변화를 주기 위해 중간에 장식자를 두는 패턴"
- 개방 폐쇄 원칙(OCP)와 의존 역전 원칙(DIP) 적용
- 장식자는 실제 서비스와 같은 이름의 메서드를 구현한다. 이때 인터페이스를 사용한다.
- 장식자는 실제 서비스에 대한 참조 변수를 갖는다(합성).
- 장식자는 실제 서비스의 같은 이름을 가진 메서드를 호출하고, 그 반환값에 장식을 더해 클라이언트에게 돌려준다.
- 장식자는 실제 서비스의 메서드 호출 전후에 별도의 로직을 수행할 수도 있다.
4. 싱글턴 패턴(Singleton Pattern)
- 싱글턴 패턴: "클래스의 인스턴스, 즉 객체를 하나만 만들어 사용하는 패턴"
- private 생성자를 갖는다.
- 단일 객체 참조 변수를 정적 속성으로 갖는다.
- 단일 객체 참조 변수가 참조하는 단일 객체를 반환하는 getInstance() 정적 메서드를 갖는다.
- 단일 객체는 쓰기 가능한 속성을 갖지 않는 것이 정석이다.
5. 템플릿 메서드 패턴(Template Method Pattern)
- 템플릿 메서드 패턴:
- 상위 클래스에 공통 로직을 수행하는 템플릿 메서드와 하위 클래스에 오버라이딩을 강제하는 추상 메서드 또는 선택적으로 오버라이딩할 수 있는 훅(Hook) 메서드를 두는 패턴
- "상위 클래스의 견본 메서드에서 하위 클래스가 오버라이딩한 메서드를 호출하는 패턴"
- 의존 역전 원칙(DIP) 적용
템플릿 메서드 패턴의 구성 요소
- 템플릿 메서드: 공통 로직을 수행, 로직 중에 하위 클래스에서 오버라이딩한 추상 메서드/훅 메서드를 호출
- 템플릿 메서드에서 호출하는 추상 메서드: 하위 클래스가 반드시 오버라이딩해야 한다.
- 템플릿 메서드에서 호출하는 훅(Hock, 갈고리) 메서드: 하위 클래스가 선택적으로 오버라이딩한다.
6. 팩터리 메서드 패턴(Factory Method Pattern)
- 팩터리 메서드 패턴:
- 팩터리: 공장
- "오버라이드된 메서드가 객체를 반환하는 패턴"
- 의존 역전 원칙(DIP) 적용
7. 전략 패턴(Strategy Pattern)
- 전략 패턴:
- 예) 무기(전략)를 조달(생성)해서 군인(컨텍스트)에게 지급(주입)해 줄 보급 장교(클라이언트, 제3자)
- 자바 언어에서는 상속 제한이 있어 템플릿 메서드 패턴보다는 전략 패턴이 활용된다.
- "클라이언트가 전략을 생성해 전략을 실행할 컨텍스트에 주입하는 패턴"
- 개방 폐쇄 원칙(OCP)과 의존 역전 원칙(DIP) 적용
전략 패턴의 구성 요소
- 전략 메서드를 가진 전략 객체
- 전략 객체를 사용하는 컨텍스트(전략 객체의 사용자/소비자)
- 전략 객체를 생성해 컨텍스트에 주입하는 클라이언트(제3자, 전략 객체의 공급자)
8. 템플릿 콜백 패턴(Template Callback Pattern - 견본/회신 패턴)
- 템플릿 콜백 패턴:
- 스프링의 3대 프로그래밍 모델 중 하나인 DI(의존성 주입)에서 사용하는 특별한 형태의 전략 패턴
- 스프링을 이해하고 활용하기 위해서는 전략 패턴, 템플릿 콜백 패턴, 리팩터링된 템플릿 콜백 패턴을 잘 기억해 둔다.
- "전략을 익명 내부 클래스로 구현한 전략 패턴"
- 개방 폐쇄 원칙(OCP)과 의존 역전 원칙(DIP) 적용
9. 스프링이 사랑한 다른 패턴들
스프링 MVC의 경우, 다음과 같은 패턴을 활용한다.
- 프론트 컨트롤러 패턴(Front Controller Pattern: 최전선 제어자 패턴):
- 프론트 컨트롤러: 모든 요청의 진입점을 하나로 통합해 관리하는 객체
- "하나의 진입점을 통해 공통 처리 로직(로깅, 인증, 인코딩 등)을 모듈화하여 각 요청 처리의 중복을 줄이는 패턴"
- 예) 스프링 MVC의 DispatcherServlet
- 요청이 들어오면 먼저 프론트 컨트롤러가 요청을 받아 처리 흐름을 제어한다.
- 요청 URI에 따라 적절한 컨트롤러로 분기한다.
- 공통 관심사를 필터나 인터셉터로 처리하기 유리하다.
- MVC 패턴(Model-View-Controller):
- Model: 비즈니스 로직과 데이터를 처리
- View: 사용자에게 보여지는 UI
- Controller: 사용자의 요청을 받아 처리 흐름을 제어
- "응용 프로그램을 세 부분으로 분리하여 관심사를 명확히 분리하는 구조"
- 유지보수성과 테스트 용이성을 높이고, 코드의 응집도와 결합도를 조절할 수 있다.
- 사용자가 버튼을 클릭하면 컨트롤러가 요청을 받아 모델을 호출한다.
- 모델은 로직을 처리하고 데이터를 반환한다.
- 컨트롤러는 그 데이터를 기반으로 적절한 뷰를 선택해 응답을 전달한다.
위에서 정리한 디자인 패턴 표를 간단히 요약하면 다음과 같다.
패턴 이름 | 핵심 개념 | 스프링 적용 예 | 적용 원칙 |
어댑터 | 호환되지 않는 인터페이스를 변환 | Spring MVC의 HandlerAdapter | - |
프록시 | 제어 흐름을 위해 대리 객체 사용 | Spring AOP, @Transactional | OCP, DIP |
데코레이터 | 기능을 동적으로 추가 | BeanPostProcessor | OCP, DIP |
싱글턴 | 하나의 인스턴스만 유지 | 스프링 빈(기본 싱글턴) | - |
템플릿 메서드 | 공통 로직 + 추상화된 확장 포인트 | JdbcTemplate, AbstractController | DIP |
팩터리 메서드 | 객체 생성을 하위 클래스에 위임 | BeanFactory, ApplicationContext | DIP |
전략 | 알고리즘을 런타임에 주입 | Validation 전략, 정책 클래스 | OCP, DIP |
템플릿 콜백 | 전략을 익명 내부 클래스로 구현 | JdbcTemplate, execute(callback) | OCP, DIP |
프론트 컨트롤러 | 공통 진입점으로 요청을 처리 | DispatcherServlet | - |
MVC | 모델-뷰-컨트롤러로 분리 | Spring MVC 구조 | SRP, SoC |
디자인 패턴 적용 (쇼핑몰 서비스)
✅ 목표
- 주문/결제 흐름에 전략, 어댑터, 템플릿 콜백 패턴을 적용한다.
- OrderService는 의존성에 묶이지 않고 유연하게 동작한다.
- 결제 수단 및 주문 방식 확장이 쉬운 구조로 구현한다.
// 핵심 클래스 구조 요약
OrderTemplate ← 템플릿 콜백 패턴
└─ NormalOrder (extends) – 기본 주문
└─ GiftOrder (extends) – 선물 주문
PaymentStrategy ← 전략 패턴
└─ KakaoPayStrategy
└─ TossPayStrategy
PaymentAdapter ← 어댑터 패턴
└─ KakaoPayAdapter
└─ TossPayAdapter
OrderTemplateFactory – 주문 템플릿 생성
PaymentStrategyFactory – 결제 전략 선택
OrderService – 주문 처리 진입점
1) PaymentAdapter & PG 어댑터 (어댑터 패턴)
/* 외부 PG사의 서로 다른 API를 내부에서 통일된 방식으로 사용 */
public interface PaymentAdapter {
void process(int amount); // 결제 요청 메서드
}
/* 외부 PG 호출 로직을 숨기고 내부 인터페이스에 맞게 변환 */
@Component
public class KakaoPayAdapter implements PaymentAdapter {
@Override
public void process(int amount) {
System.out.println("[Kakao PG] " + amount + "원 결제 요청");
// TODO: HTTP 요청 등 외부 API 호출 구현
}
}
@Component
public class TossPayAdapter implements PaymentAdapter {
@Override
public void process(int amount) {
System.out.println("[Toss PG] " + amount + "원 결제 요청");
}
}
2) PaymentStrategy (전략 패턴)
/* 결제 방식을 캡슐화 */
public interface PaymentStrategy {
String getCode(); // 결제 방식 식별용 코드 (예: "kakao", "toss")
void pay(int amount); // 결제 메서드
}
@Component
@RequiredArgsConstructor
public class KakaoPayStrategy implements PaymentStrategy {
private final KakaoPayAdapter adapter;
@Override
public String getCode() {
return "kakao";
}
@Override
public void pay(int amount) {
adapter.process(amount); // 어댑터를 통해 외부 API 호출
}
}
@Component
@RequiredArgsConstructor
public class TossPayStrategy implements PaymentStrategy {
private final TossPayAdapter adapter;
@Override
public String getCode() {
return "toss";
}
@Override
public void pay(int amount) {
adapter.process(amount);
}
}
3) OrderTemplate (템플릿 콜백 패턴)
public abstract class OrderTemplate {
// 순서대로 실행: validate → pay → save → after
public final void processOrder(int amount) {
validate();
pay(amount);
saveOrder();
afterOrder();
}
// 하위 클래스에서 구체화 (콜백)
protected abstract void validate(); // 유효성 검증
protected abstract void pay(int amount); // 결제
protected abstract void saveOrder(); // 주문 저장
protected void afterOrder() {} // Hook 메서드 (예: 배송 생성)
}
4) 주문 템플릿 구현체
/* 구체적인 주문 처리 방식 */
public class NormalOrder extends OrderTemplate {
private final PaymentStrategy paymentStrategy; // 결제 전략 주입
public NormalOrder(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
@Override
protected void validate() {
System.out.println("주문 유효성 검증");
}
@Override
protected void pay(int amount) {
paymentStrategy.pay(amount); // 전략 실행 (결제 수단에 따라 분기됨)
}
@Override
protected void saveOrder() {
System.out.println("Order 저장 로직 실행");
}
@Override
protected void afterOrder() {
System.out.println("배송 정보 생성");
}
}
5) 전략 팩토리 & 템플릿 팩토리
/* 등록된 모든 결제 전략을 자동으로 수집하여, 코드값으로 전략을 반환 */
@Component
public class PaymentStrategyFactory {
private final Map<String, PaymentStrategy> strategyMap;
// 스프링이 자동으로 모든 PaymentStrategy 구현체를 List로 주입
public PaymentStrategyFactory(List<PaymentStrategy> strategies) {
this.strategyMap = strategies.stream()
.collect(Collectors.toMap(PaymentStrategy::getCode, Function.identity()));
}
public PaymentStrategy getStrategy(String code) {
// TODO: 결제 코드 전략 구현
return strategyMap.getOrDefault(code, amount -> {
throw new IllegalArgumentException("지원하지 않는 결제 수단입니다.");
});
}
}
/* 결제 전략을 받아 주문 템플릿을 생성 */
@Component
@RequiredArgsConstructor
public class OrderTemplateFactory {
private final PaymentStrategyFactory strategyFactory;
public OrderTemplate create(String paymentCode) {
PaymentStrategy strategy = strategyFactory.getStrategy(paymentCode);
return new NormalOrder(strategy); // 템플릿에 전략 주입
}
}
6) 주문 서비스 클래스
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderTemplateFactory orderTemplateFactory;
public void order(String paymentMethod, int amount) {
// paymentMethod에 따라 템플릿 생성 및 실행
OrderTemplate order = orderTemplateFactory.create(paymentMethod);
order.processOrder(amount); // 템플릿 메서드 실행
}
}
패턴 | 적용 위치 | 설명 |
전략 패턴 | 다양한 결제 수단 선택 (Kakao, Toss) | PaymentStrategy를 런타임에 주입하여 유연하게 변경 |
어댑터 패턴 | 외부 PG사 API → 내부 통일 인터페이스 | PaymentAdapter를 통해 외부 API 호출을 감춤 |
템플릿 콜백 패턴 | 주문 처리 공통 흐름 관리 | OrderTemplate에 공통 흐름 정의 + 후처리 확장 |
이렇게 패턴을 적용한 구조의 장점은 다음과 같이 정리할 수 있다.
- 확장성: 결제 수단, 주문 유형이 늘어나도 기존 코드를 수정할 필요가 없다.
- 유지보수성: 패턴별로 책임이 분리되어 있어 각 기능 테스트가 용이하다.
- 의존성 최소화: OrderService는 템플릿 팩토리에만 의존하며, 구체 클래스와는 무관하다.
이 글은 『스프링 입문을 위한 자바 객체 지향의 원리와 이해』 책을 학습한 내용을 정리한 것입니다.
'프로그래밍 > 객체지향' 카테고리의 다른 글
[스터디5] 06. 쇼핑몰 서비스 - 도메인 모델, 유스케이스, 책임-주도 설계 (0) | 2025.04.05 |
---|---|
[스터디6] 05. 객체 지향 설계 5원칙 - SOLID (0) | 2025.03.29 |
[스터디6] 04. 자바가 확장한 객체 지향 (0) | 2025.03.22 |
[스터디6] 03. 자바와 객체지향 (0) | 2025.03.15 |
[스터디6] 02. 자바와 절차적/구조적 프로그래밍 (1) | 2025.03.07 |
Comments