1. Strategy 패턴이란?

Strategy 패턴은 행위를 캡슐화한 알고리즘을 런타임에 바꿀 수 있도록 하는 디자인 패턴입니다. 즉, 특정 행위(전략)를 사용하는 주체가 그 구현을 직접 알지 않아도 되고, 실행 시점에 전략을 선택하거나 변경할 수 있도록 하여 유연성과 확장성을 극대화합니다.

사용 목적

  • 조건에 따라 다양한 행위를 적용할 수 있도록 분리
  • 코드 변경 없이 알고리즘 교체 가능
  • if/else 또는 switch 문을 객체 구조로 치환

2. 구조와 동작 원리

기본 구조

Context
 ├─ Strategy 인터페이스 타입을 필드로 보유
 └─ setStrategy() 로 전략 주입 가능

Strategy (인터페이스)
 ├─ ConcreteStrategyA
 └─ ConcreteStrategyB

예시 코드

public interface PaymentStrategy {
    void pay(int amount);
}

public class CreditCardStrategy implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("신용카드로 결제: " + amount);
    }
}

public class KakaoPayStrategy implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("카카오페이로 결제: " + amount);
    }
}

public class PaymentContext {
    private PaymentStrategy strategy;

    public void setStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public void pay(int amount) {
        strategy.pay(amount);
    }
}

3. 어떤 문제를 해결하기 위해 자주 쓰일까?

전략 적용의 일반적 배경

기존에는 하나의 클래스 내에서 다양한 조건(if-else 또는 switch)으로 로직을 분기하는 경우가 많았습니다. 예를 들어 결제 서비스 하나에 카드, 카카오페이, 포인트 등 여러 결제 방식이 혼재돼 있는 경우, 코드가 복잡해지고 유지보수가 어려워지며 테스트 또한 힘들어집니다.

Strategy 패턴을 적용하면 각 결제 수단별로 클래스를 분리하여 관리할 수 있습니다. 이렇게 하면 클래스 수는 증가하지만 각 전략 클래스가 단일 책임 원칙(SRP)을 만족하게 되어 가독성과 확장성이 훨씬 높아집니다.

런타임에 다른 알고리즘을 사용하는 이유

Strategy 패턴의 핵심은 행위를 캡슐화해서 실행 중에 전략을 동적으로 교체할 수 있다는 점입니다. 사용자 입력이나 환경 설정, 혹은 요청 컨텍스트에 따라 적절한 전략을 런타임에 선택해 적용하는 것이 가능합니다.

예를 들어 결제 시스템에서 사용자가 '카드', '카카오페이', '포인트' 중 어떤 결제 방식을 사용할지 모른다면, 실행 시점에 맞는 전략 객체를 주입해 결제를 수행할 수 있습니다.

주요 활용 케이스

  • 조건 분기 제거: 전략 객체로 교체
  • 런타임에 알고리즘이 바뀌는 경우 (결제 방식, 정렬 방식 등)
  • OCP(Open-Closed Principle) 준수: 기존 코드를 수정하지 않고 전략 추가 가능

4. Spring에서 실전 활용 예시

1) Bean 주입으로 전략 교체

@Component
public class PaymentService {
    private final Map<String, PaymentStrategy> strategyMap;

    public PaymentService(List<PaymentStrategy> strategies) {
        this.strategyMap = strategies.stream()
            .collect(Collectors.toMap(
                s -> s.getClass().getSimpleName().replace("Strategy", "").toLowerCase(),
                s -> s
            ));
    }

    public void pay(String type, int amount) {
        PaymentStrategy strategy = strategyMap.get(type);
        strategy.pay(amount);
    }
}

2) @Qualifier로 특정 전략 명시 주입

  • 복수 전략 중 하나만 사용할 경우 명시적으로 주입

3) enum 기반 전략 매핑

전략 선택 시 문자열 대신 enum 타입을 사용하는 방식은 타입 안정성과 명시성이 뛰어납니다.

public enum PaymentType {
    CARD, KAKAO
}
@Component
public class PaymentService {
    private final Map<PaymentType, PaymentStrategy> strategyMap;

    public PaymentService(List<PaymentStrategy> strategies) {
        this.strategyMap = strategies.stream()
            .collect(Collectors.toMap(
                s -> PaymentType.valueOf(s.getClass().getSimpleName().replace("Strategy", "").toUpperCase()),
                s -> s
            ));
    }

    public void pay(PaymentType type, int amount) {
        PaymentStrategy strategy = strategyMap.get(type);
        strategy.pay(amount);
    }
}

5. 장단점

장점단점

런타임 전략 교체 가능 클래스 수 증가 가능
조건문 제거로 가독성 향상 전략 선택을 위한 관리 코드 필요
OCP 원칙 충족 전략 간 공통 인터페이스 설계가 중요함

6. 실무 적용 팁

  • 전략이 많다면 Map 주입 방식 추천 (Spring에서는 자동 주입 가능)
  • 전략 간 입력값이 다르면 공통 인터페이스 설계 또는 래퍼 객체 사용 고려
  • enum + 함수형 인터페이스 조합도 최근 자주 사용됨

7. 마무리 요약

Strategy 패턴은 조건 분기 대신 전략 객체를 통해 행위를 유연하게 교체할 수 있게 해주는 구조입니다. 실행 중 전략을 교체할 수 있고, 새로운 전략 추가 시 기존 코드를 수정하지 않아도 되므로 유지보수와 확장성 면에서 매우 유리합니다. Spring의 DI와 결합하면 실전에서 더 효과적으로 활용할 수 있습니다.

 

1. Template Method 패턴이란?

Template Method 패턴은 알고리즘의 뼈대를 상위 클래스에서 정의하고, 구체적인 구현은 하위 클래스에 위임하는 디자인 패턴입니다. 핵심 아이디어는 "공통된 처리 흐름은 고정하되, 세부 구현만 바꾼다"는 데 있습니다.

사용 목적

  • 중복되는 처리 로직을 공통화
  • 알고리즘 구조는 고정하되, 세부 내용만 유연하게 확장
  • 코드의 일관성과 유지보수성 향상

2. 구조와 동작 원리

기본 구조

AbstractClass
 ├─ templateMethod()     ← 고정된 처리 순서 정의
 ├─ stepOne()            ← 추상 메서드 → 하위 클래스 구현
 └─ stepTwo()            ← hook 메서드 (옵션)

3. 어떤 문제를 해결하기 위해 자주 쓰일까?

Template Method 패턴은 다음과 같은 문제 상황에서 자주 사용됩니다:

1) 다수의 유사한 프로세스 흐름이 존재할 때

  • 예: 보고서 생성, 배치 작업, 외부 API 호출 등
  • 공통된 흐름이 있고, 일부 단계만 서로 다를 때 추상 클래스로 구조화

2) 타 시스템 연계/인터페이스 처리

  • 예: 외부 API 호출 시 RestTemplate, WebClient 등의 사용이 반복될 경우
  • 공통적인 요청 흐름(로그 남기기, 요청 생성, 응답 파싱 등)을 상위 클래스에서 고정하고, API별 세부 구현은 하위 클래스에서 처리
  • 공통 모듈화로 재사용성과 유지보수성 향상

3) 중복 제거 + 유지보수 단순화가 필요한 레거시 시스템 정리 시

  • 비슷한 로직이 퍼져 있는 코드를 통일된 상속 기반으로 재구성

핵심은 "공통된 처리 흐름을 고정하고, 변화가 필요한 지점만 유연하게 대체"하는 데 있습니다. 이 구조는 상위에 공통 추상 클래스를 만들고, 이를 상속받아 구체적인 동작을 구현하는 방식으로 이루어집니다.


4. 장단점

 

장점 단점
알고리즘 구조를 재사용 가능 상속 기반이라 유연성 제한 있음
로직 중복 최소화 하위 클래스 증가로 관리 부담 가능
일관된 흐름 보장 모든 단계가 추상화되면 복잡해질 수 있음

5. 실무 적용 팁

  • 같은 흐름의 처리 과정에서 다형성을 활용하고 싶을 때 고려
  • AbstractService, AbstractHandler 등으로 응용 가능

6. 마무리 요약

  • Template Method는 상위 클래스에서 전체 처리 흐름을 정의하고, 하위 클래스에서 각 단계의 로직을 세분화하는 패턴입니다.
  • 추상 클래스를 통해서 공통 흐름은 고정하고, 구체적인 처리만 커스터마이징하고 싶을 때 적합합니다.
  • 상속 구조가 강제되므로, 복잡한 상황에서는 전략 패턴과 조합하여 설계하는 것도 고려해야 합니다.
 

1. Proxy 패턴이란?

Proxy 패턴은 어떤 객체에 대한 접근을 제어하거나, 그 객체를 대신하여 추가 작업을 수행할 수 있도록 중간 대리 객체(Proxy)를 두는 디자인 패턴입니다.

사용 목적

  • 실제 객체의 접근 제어
  • 객체 생성 비용이 클 때 지연 로딩
  • 부가 기능 부여 (로그, 인증, 캐시 등)

적용 사례

  • 데이터베이스 연결 풀 관리
  • 보안 검사/권한 제어
  • Spring의 AOP 기반 기능들 (@Transactional, @Async, @Cacheable 등)

2. 구조와 동작 원리

기본 구조

클라이언트 → Proxy → 실제 객체(RealSubject)

예시 클래스 구조

public interface Service {
    void doSomething();
}

public class RealService implements Service {
    public void doSomething() {
        System.out.println("Real service logic");
    }
}

public class LoggingProxy implements Service {
    private final Service target;

    public LoggingProxy(Service target) {
        this.target = target;
    }

    public void doSomething() {
        System.out.println("[Log] Before");
        target.doSomething();
        System.out.println("[Log] After");
    }
}

3. Spring에서 Proxy 패턴 적용 예시

Spring에서는 Proxy 기반 AOP 구현이 핵심 메커니즘입니다. 실제 객체 앞에 프록시 객체를 두어 횡단 관심사를 처리합니다.

대표 예시: @Transactional

@Service
public class OrderService {
    @Transactional
    public void placeOrder() {
        // 트랜잭션 처리 전/후 프록시가 개입
    }
}

이때 Spring은 OrderService의 프록시를 생성하여, 메서드 실행 전후에 트랜잭션을 시작하고 커밋 또는 롤백합니다.


4. 장단점

장점단점

장점 단점
원래 객체 수정 없이 부가 기능 삽입 가능 클래스 수 증가, 구조 복잡도 증가
접근 제어, 보안, 로깅 등 분리 가능 성능 오버헤드 가능성 있음
AOP, 리모팅, 캐시 등 확장 기능에 유리 디버깅 난이도 상승

5. 실무 적용 팁

  • 횡단 관심사(Cross-cutting concern) 가 많다면 Proxy 또는 AOP 적용 고려
  • 무분별한 프록시 체인 생성은 성능 저하 원인이 될 수 있음
  • Spring에서는 @Aspect, @Around, @Before 등을 통해 선언형 AOP를 쉽게 구현 가능
@Aspect
@Component
public class LogAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void beforeLog() {
        System.out.println("[LOG] 메서드 실행 전");
    }
}

6. 실무에서 Proxy 패턴은 어디까지 쓰는가?

Spring에서 Proxy 패턴(AOP 포함)은 대부분 횡단 관심사(부가 기능) 를 분리해 처리하는 용도로 사용됩니다. 다음과 같은 기능들이 대표적입니다:

활용 예시 설명
@Transactional 트랜잭션 시작/커밋/롤백을 자동으로 처리
@Cacheable 메서드 결과 캐싱 및 캐시 무효화 관리
@Async 비동기 실행 처리
@PreAuthorize 메서드 실행 전 권한 검사
@Before, @After 메서드 실행 전후 로깅 또는 감사 처리

이처럼 대부분의 AOP 기반 기능은 실제 비즈니스 로직은 건드리지 않고, 그 앞뒤에서 감싸는 형태로 구현됩니다.

Proxy 패턴의 전형적인 실전 활용입니다.

💡 핵심 로직(예: 주문 생성, 결제 처리 등)은 여전히 Controller → Service → Repository 구조 내에서 구현되며, 프록시는 이를 감시하거나 감싸는 역할만 수행합니다.

Proxy를 통해 로직 자체를 변경하거나 처리하는 일은 매우 드물며, 오히려 설계를 복잡하게 만들고 유지보수를 어렵게 만들 수 있습니다. 따라서 프록시는 부가 기능 전용으로 활용하고, 핵심 비즈니스는 명확히 계층 구조 안에서 처리하는 것이 일반적인 설계 방향입니다.


7. 마무리 요약

  • Proxy 패턴은 실제 객체에 대한 접근을 제어하거나, 실행 전후로 로직을 삽입할 수 있는 구조입니다.
  • Spring AOP는 이러한 Proxy 패턴을 기반으로 작동하며, 트랜잭션, 보안, 로깅 등의 핵심 기능을 지원합니다.
  • 코드 분리와 유지보수성을 위해, 필요한 경우에만 명확하게 프록시를 도입해야 합니다.
 

+ Recent posts