개발인생/Backend

[Spring] Spring Boot 개발자를 위한 필수 디자인 패턴 6가지

forri 2025. 2. 25. 20:47

 

디자인 패턴 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, 설정, 캐시)
  • 팩토리 → 객체 생성의 핵심, 유연한 구조 유지
  • 전략 패턴 → 알고리즘 변경이 필요한 시스템에서 유용
  • 옵저버 & 프록시 → 특정 도메인에서 중요하지만, 모든 프로젝트에 필수는 아님
  • 빌더 → 사용되는 곳은 많지만, 필수적인 패턴은 아님