
디자인 패턴 6가지 정리 (Spring Boot, Java 백엔드 개발 환경)
1. 생성 패턴(Creational Patterns) → 객체 생성 방식 최적화
싱글톤: 프로그램에서 하나의 인스턴스만 유지
팩토리: 객체 생성을 서브클래스에 위임
빌더: 복잡한 객체 생성 과정을 분리해 유연성 제공
2. 구조 패턴(Structural Patterns) → 클래스와 객체 관계 최적화
프록시: 원래 객체에 대한 접근을 조정하는 대리 객체 제공
3. 행동 패턴(Behavioral Patterns) → 객체 간의 커뮤니케이션 최적화
전략: 실행 중에 알고리즘을 변경할 수 있도록 인터페이스 제공
옵저버: 상태 변경을 감지하고 여러 객체에게 알림
1. 싱글톤 패턴 (Singleton) → 가장 많이 사용됨 (★★★★★)
- 어디서?
✅ DB 연결 (JDBC, Hibernate 등)
✅ 로깅 (Logger)
✅ 설정 관리 (Configuration, Environment)
✅ 캐시 (Spring의 Bean 객체 관리 등)- 왜 중요해?
- 하나의 인스턴스를 유지하여 리소스 낭비를 줄이고 효율적 관리
- 다중 스레드 환경에서 안정적인 공유 객체 제공
예제 (Spring Bean에서 Singleton 사용)
@Service
public class SingletonService {
private static final SingletonService instance = new SingletonService();
private SingletonService() {} // private 생성자
public static SingletonService getInstance() {
return instance;
}
}
✔ Spring에서는 기본적으로 Bean이 싱글톤으로 관리됨!
@Service // @Component, @Repository, @Controller도 기본적으로 싱글톤
public class UserService { }
2. 팩토리 패턴 (Factory Pattern) → 객체 생성의 핵심 (★★★★★)
- 어디서?
✅ 스프링 프레임워크의 BeanFactory
✅ JDBC에서 DriverManager.getConnection()
✅ 인터페이스 기반 객체 생성 (유연한 구조)- 왜 중요해?
- 객체 생성 로직을 캡슐화하여 코드 수정 없이 새로운 객체 추가 가능
- 인터페이스 기반으로 동작하므로 유지보수 용이
예제 (간단한 팩토리 클래스)
public class ProductFactory {
public static Product createProduct(String type) {
if ("A".equals(type)) {
return new ProductA();
} else if ("B".equals(type)) {
return new ProductB();
}
throw new IllegalArgumentException("Unknown product type");
}
}
Product product = ProductFactory.createProduct("A");
✔ Spring에서는 BeanFactory, ApplicationContext가 이 패턴을 사용함.
3. 빌더 패턴 (Builder Pattern) → 객체 생성 (★★☆☆☆)
- 어디서?
✅ StringBuilder
✅ 복잡한 객체 생성 (ex: JSON, XML, HTTP 요청)- 왜 중요해?
- 생성자에 너무 많은 매개변수가 있을 때 유용
- 가독성 높은 객체 생성 가능
예제 (빌더 패턴 적용)
public class User {
private String name;
private int age;
private User(UserBuilder builder) {
this.name = builder.name;
this.age = builder.age;
}
public static class UserBuilder {
private String name;
private int age;
public UserBuilder setName(String name) {
this.name = name;
return this;
}
public UserBuilder setAge(int age) {
this.age = age;
return this;
}
public User build() {
return new User(this);
}
}
}
User user = new User.UserBuilder()
.setName("John")
.setAge(30)
.build();
✔ Lombok의 @Builder 어노테이션으로 쉽게 적용 가능!
@Getter
@Builder
public class UserDTO {
private String name;
private int age;
}
4. 전략 패턴 (Strategy Pattern) → 유연한 알고리즘 변경 (★★★★☆)
- 어디서?
✅ 결제 시스템 (카드/계좌이체/포인트)
✅ 데이터 압축 방식 변경 (ZIP, RAR, TAR)
✅ 정렬 알고리즘 변경 (퀵소트, 머지소트 등)- 왜 중요해?
- 실행 중 알고리즘을 변경할 수 있도록 분리
- 유지보수성이 뛰어나고 OCP(개방-폐쇄 원칙) 적용 가능
예제
✔ 전략 인터페이스 정의
public interface PaymentStrategy {
void pay(int amount);
}
public class CreditCardPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println(amount + "원을 신용카드로 결제했습니다.");
}
}
public class PaypalPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println(amount + "원을 PayPal로 결제했습니다.");
}
}
✔ 전략을 실행하는 클래스
public class PaymentContext {
private PaymentStrategy strategy;
public PaymentContext(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void executePayment(int amount) {
strategy.pay(amount);
}
}
✔ 전략을 유연하게 변경 가능!
PaymentContext context = new PaymentContext(new CreditCardPayment());
context.executePayment(10000);
context = new PaymentContext(new PaypalPayment());
context.executePayment(20000);
5. 프록시 패턴 (Proxy Pattern) → 접근 제어 (★★★☆☆)
- 어디서?
✅ Spring AOP (Aspect-Oriented Programming)
✅ 데이터베이스 Lazy Loading
✅ API Rate Limiting- 왜 중요해?
- 원래 객체에 대한 접근을 제어하는 대리 역할
- 성능 최적화 (ex: 데이터베이스 요청 지연 로딩)
예제 (프록시 클래스)
public class RealService implements Service {
public void request() {
System.out.println("실제 서비스 요청 실행");
}
}
public class ProxyService implements Service {
private RealService realService;
public void request() {
if (realService == null) {
realService = new RealService();
}
System.out.println("프록시를 통해 요청 전달");
realService.request();
}
}
✔ Spring AOP에서 사용되는 대표적인 패턴!
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("메서드 실행 전 로그: " + joinPoint.getSignature().getName());
}
}
6. 옵저버 패턴 (Observer Pattern) → 이벤트 시스템 (★★★☆☆)
- 어디서?
✅ GUI 이벤트 리스너 (버튼 클릭 등)
✅ 웹소켓 (WebSocket) 및 메시지 브로커 (Kafka, RabbitMQ)
✅ 블로그 구독 시스템 (새 글 알림)- 왜 중요해?
- 한 객체의 변경이 여러 객체에 자동 반영됨
- 이벤트 기반 시스템에서 광범위하게 사용
예제 (옵저버 등록 및 알림)
public interface Observer {
void update(String message);
}
public class User implements Observer {
private String name;
public User(String name) {
this.name = name;
}
public void update(String message) {
System.out.println(name + "님, 새로운 알림: " + message);
}
}
public class NotificationService {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void notifyAllUsers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
NotificationService service = new NotificationService();
service.addObserver(new User("홍길동"));
service.addObserver(new User("이순신"));
service.notifyAllUsers("새로운 상품이 추가되었습니다!");
✔ 실시간 이벤트 기반 시스템에서 필수 패턴! (ex: Spring Event, Kafka)
✔️ 정리
| 순위 | 패턴 | 실무 활용도 | 주요사용 |
| 1️⃣ | 싱글톤 | ★★★★★ | DB, 설정, 캐시, 로깅 |
| 2️⃣ | 팩토리 | ★★★★★ | 객체 생성 관리, DI (의존성 주입) |
| 3️⃣ | 전략 | ★★★★☆ | 결제 방식 변경, 알고리즘 선택 |
| 4️⃣ | 옵저버 | ★★★☆☆ | 이벤트 시스템, GUI, 웹소켓 |
| 5️⃣ | 프록시 | ★★★☆☆ | AOP, Lazy Loading |
| 6️⃣ | 빌더 | ★★☆☆☆ | 복잡한 객체 생성 |
- 싱글톤 → 실무에서 가장 많이 사용됨 (DB, 설정, 캐시)
- 팩토리 → 객체 생성의 핵심, 유연한 구조 유지
- 전략 패턴 → 알고리즘 변경이 필요한 시스템에서 유용
- 옵저버 & 프록시 → 특정 도메인에서 중요하지만, 모든 프로젝트에 필수는 아님
- 빌더 → 사용되는 곳은 많지만, 필수적인 패턴은 아님
'개발인생 > Backend' 카테고리의 다른 글
| [Spring] Spring Data JPA (0) | 2025.03.19 |
|---|---|
| GraphQL 입문 가이드: REST API 대체할 만할까? (0) | 2025.03.07 |
| [Spring] MVC 구조 설명 | Controller, Service, Repository로 역할 분리하기 (0) | 2025.02.25 |
| [Spring] 핵심 기술 총정리 (0) | 2025.02.25 |
| [ORM] JPA 성능 최적화: N+1 문제 해결하는 3가지 방법 (0) | 2025.02.22 |