Design Patterns Series | Baeldung
크게 구분을 한다면 두갈래로 할수 있음
- 전통적인 디자인 패턴 -> 생성, 구조, 행위
- 현대적인(?) 디자인 패턴
위 baeldung에서도 전통적 디자인 패턴 3개 + Architectural Patterns으로 정리하고 있음
현실적으로 다 알고있기가 힘들지만 용어만이라도 알아두자
중요하다고 생각하는 것만 작성함
1. 싱글톤 패턴
📌 특징: 클래스의 인스턴스가 하나만 생성되도록 보장
✅ 장점
- 리소스 재사용
- 전역적 상태 관리
- 메모리 효율성
❌ 단점
- 단위 테스트 어려움
- 전역 상태로 인한 버그
- Thread-safe 구현 필요
💡 사용 시기
- 공유 리소스 관리
- 설정 정보 관리
- DB 연결 풀
🔧 사용 사례
- DB ConnectionPool
- Cache Manager
- Spring Bean의 기본 스코프
// Thread-safe Singleton
public class DatabaseConnection {
private static volatile DatabaseConnection instance;
private final Connection connection;
private DatabaseConnection() {
this.connection = createConnection();
}
public static DatabaseConnection getInstance() {
if (instance == null) {
synchronized (DatabaseConnection.class) {
if (instance == null) {
instance = new DatabaseConnection();
}
}
}
return instance;
}
// 실제 사용 예시
public void executeQuery(String sql) {
try {
PreparedStatement stmt = connection.prepareStatement(sql);
// 쿼리 실행 로직
} catch (SQLException e) {
throw new DatabaseException("쿼리 실행 실패", e);
}
}
}
// Spring에서의 Singleton
@Component
public class CacheManager {
private final Map<String, Object> cache = new ConcurrentHashMap<>();
public void put(String key, Object value) {
cache.put(key, value);
}
public Optional<Object> get(String key) {
return Optional.ofNullable(cache.get(key));
}
}
2. 전략 패턴
📌 특징: 알고리즘을 캡슐화하고 교체 가능하게 함
✅ 장점
- 알고리즘 교체 용이
- 비즈니스 로직 분리
- 확장성 좋음
❌ 단점
- 클래스 수 증가
- 전략 선택 로직 필요
💡 사용 시기
- 비즈니스 로직이 자주 변경될 때
- 조건문이 복잡할 때
🔧 사용 사례
- 결제 시스템의 다양한 결제 방식 처리
- 할인 정책 적용 (VIP, 시즌, 프로모션)
- 파일 저장 방식 (로컬, S3, FTP)
- 알림 발송 방식 (Email, SMS, Push)
// 결제 전략 인터페이스
public interface PaymentStrategy {
PaymentResult process(Order order);
}
// 구체적인 전략들
@Component
public class CreditCardStrategy implements PaymentStrategy {
@Override
public PaymentResult process(Order order) {
// 신용카드 결제 로직
return new PaymentResult(/* ... */);
}
}
@Component
public class KakaoPayStrategy implements PaymentStrategy {
@Override
public PaymentResult process(Order order) {
// 카카오페이 결제 로직
return new PaymentResult(/* ... */);
}
}
// 전략 사용
@Service
public class PaymentService {
private final Map<PaymentType, PaymentStrategy> strategies;
public PaymentService(List<PaymentStrategy> strategyList) {
strategies = strategyList.stream()
.collect(Collectors.toMap(
strategy -> getPaymentType(strategy.getClass()),
strategy -> strategy
));
}
public PaymentResult processPayment(Order order) {
PaymentStrategy strategy = strategies.get(order.getPaymentType());
if (strategy == null) {
throw new UnsupportedPaymentTypeException();
}
return strategy.process(order);
}
}
3. 팩토리 메서드 패턴
📌 특징: 객체 생성을 서브클래스에 위임
✅ 장점
- 객체 생성 로직 캡슐화
- 확장에 유연
- 결합도 감소
❌ 단점
- 클래스 계층 구조 복잡
- 코드량 증가
💡 사용 시기
- 객체 생성 로직이 복잡할 때
- 객체 생성을 유연하게 처리해야 할 때
- 객체 생성 방식을 확장할 가능성이 있을 때
🔧 실무 사례
- 결제 시스템의 결제 수단 객체 생성
- 다양한 형식의 문서 생성기
- 데이터베이스 커넥션 생성
- API 클라이언트 생성
// 기본 인터페이스
public interface PaymentProcessor {
void processPayment(Order order);
}
// 구체적인 구현체들
@Component
public class KakaoPayProcessor implements PaymentProcessor {
@Override
public void processPayment(Order order) {
// 카카오페이 결제 처리
}
}
@Component
public class CreditCardProcessor implements PaymentProcessor {
@Override
public void processPayment(Order order) {
// 신용카드 결제 처리
}
}
// Factory 클래스
@Component
public class PaymentProcessorFactory {
private final Map<PaymentMethod, PaymentProcessor> processorMap;
public PaymentProcessorFactory(List<PaymentProcessor> processors) {
processorMap = processors.stream()
.collect(Collectors.toMap(
processor -> getPaymentMethod(processor.getClass()),
processor -> processor
));
}
public PaymentProcessor getProcessor(PaymentMethod method) {
PaymentProcessor processor = processorMap.get(method);
if (processor == null) {
throw new UnsupportedPaymentMethodException(method);
}
return processor;
}
// 실제 사용 예시
@Service
public class OrderService {
private final PaymentProcessorFactory factory;
public void processOrder(Order order) {
PaymentProcessor processor = factory.getProcessor(order.getPaymentMethod());
processor.processPayment(order);
}
}
}
// 다른 실무 예시: API 클라이언트 생성
public interface ApiClient {
Response execute(Request request);
}
@Component
public class ApiClientFactory {
private final RestTemplateBuilder restTemplateBuilder;
private final WebClientBuilder webClientBuilder;
public ApiClient createClient(ClientType type, String baseUrl) {
switch (type) {
case REST_TEMPLATE:
return new RestTemplateApiClient(
restTemplateBuilder
.setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(5))
.rootUri(baseUrl)
.build()
);
case WEB_CLIENT:
return new WebClientApiClient(
webClientBuilder
.baseUrl(baseUrl)
.filter(logRequest())
.build()
);
default:
throw new IllegalArgumentException("Unsupported client type");
}
}
}
'개발 > 면접' 카테고리의 다른 글
분산환경에서 트랜잭션 관리하는 방법 (0) | 2025.04.10 |
---|---|
코딩테스트/기술블로그/메일링 모음 (0) | 2025.03.10 |
디자인 패턴 아는척하기 (2) - 현대적인 디자인 패턴 (1) | 2024.12.25 |
백엔드 개발 면접 질문 모음 (2) | 2024.12.21 |