Interview

백엔드 개발자가 자주 쓰는 디자인 패턴 5가지 — 면접 단골 질문 정리

GoF 23개 패턴을 다 외울 필요 없어요. Singleton, Factory Method, Strategy, Observer, Proxy — 백엔드 주니어 면접에서 자주 묻는 5개 패턴을 한 줄 정의·코드·follow-up 질문까지 한 번에 정리했어요.
2026.05.22
백엔드 개발자가 자주 쓰는 디자인 패턴 5가지 — 면접 단골 질문 정리

"디자인 패턴 책은 두꺼운데, 면접에서는 결국 몇 개만 묻는다던데요?"

주니어 백엔드 면접을 준비하면서 한 번쯤 들어보셨을 거예요. GoF의 23가지 패턴을 전부 외우는 건 현실적이지 않고, 그렇다고 모르고 가면 "Spring의 @Transactional은 어떤 패턴으로 동작하나요?" 같은 질문에서 막히게 돼요.

이 글에서는 Spring Boot 기반 백엔드 면접에서 자주 등장하는 5개 패턴만 골라, 각 패턴마다 1) 한 줄 정의 2) 언제 쓰는지 3) 짧은 코드 예시 4) 면접관이 던지는 follow-up 질문

순서로 정리했어요. 외워야 할 양은 줄이고, 면접에서 실제로 답할 수 있는 정도로 다져 보세요.

이미지 placeholder — alt: "주니어 백엔드 개발자가 노트북 앞에서 디자인 패턴을 정리하는 일러스트"


디자인 패턴, 왜 면접에서 묻는 걸까요?

면접관이 패턴 이름을 외웠는지 평가하려는 게 아니에요. 상황을 보고 적절한 도구를 고를 수 있는가, 트레이드오프를 설명할 수 있는가를 보려는 거죠.

특히 Spring/Java 진영의 백엔드 면접에서는 패턴이 단순한 이론이 아니라 프레임워크 동작 원리와 직결돼요. @Transactional은 Proxy로, ApplicationEventPublisher는 Observer로 만들어져 있거든요. 패턴을 알면 프레임워크의 "마법 같은 동작"을 자기 언어로 설명할 수 있어요.

그래서 우아한형제들 같은 국내 대형 백엔드 조직도 객체지향과 디자인 패턴을 꾸준히 기술 블로그에서 다뤄요[11].


1. Singleton — 인스턴스를 딱 하나만

한 줄 정의: 클래스가 단 하나의 인스턴스만 갖도록 보장하고, 전역 접근점을 제공하는 생성 패턴이에요[1].

언제 쓰나요? 설정값처럼 애플리케이션 전체에서 하나만 있으면 충분한 객체, 또는 DB 연결 풀처럼 비싼 자원을 공유해야 할 때 써요.

Java 예시 (thread-safe double-checked locking):

```java public class AppConfig { private static volatile AppConfig instance;

private AppConfig() {}

public static AppConfig getInstance() { if (instance == null) { synchronized (AppConfig.class) { if (instance == null) { instance = new AppConfig(); } } } return instance; } } ```

Kotlin에서는 object 키워드 한 줄이면 끝나요.

``kotlin object AppConfig { val timeoutMs: Long = 3000 } ``

면접 follow-up:

  • "멀티스레드 환경에서 안전한가요?" → 단순한 if (instance == null) instance = new ... 은 race condition 발생. volatile + double-checked locking 또는 정적 inner class(holder) 방식으로 해결한다고 답하면 좋아요[1].

  • "Spring의 singleton과 GoF singleton은 같은 건가요?"Spring singleton은 ApplicationContext 당 하나이고, GoF singleton은 JVM 전역 하나라는 점이 핵심 차이예요. Spring Bean의 기본 스코프가 singleton이라는 사실은 공식 문서에 명시돼 있어요[6].


2. Factory Method — 객체 생성을 위임

한 줄 정의: 슈퍼클래스에서 객체 생성 인터페이스를 정의하고, 어떤 구체 타입을 만들지는 서브클래스가 결정하게 하는 생성 패턴이에요[2].

언제 쓰나요? if (type == "kakao") new KakaoPay() else if (type == "naver") new NaverPay() 같은 분기로 객체를 만드는 코드가 보일 때, Factory Method로 빼면 깔끔해져요.

Kotlin 예시 (결제 수단 생성):

```kotlin interface PaymentProcessor { fun pay(amount: Long) }

class KakaoPayProcessor : PaymentProcessor { override fun pay(amount: Long) { / ... / } }

class NaverPayProcessor : PaymentProcessor { override fun pay(amount: Long) { / ... / } }

object PaymentProcessorFactory { fun of(type: String): PaymentProcessor = when (type) { "kakao" -> KakaoPayProcessor() "naver" -> NaverPayProcessor() else -> throw IllegalArgumentException("Unknown payment: $type") } } ```

면접 follow-up:

  • "Abstract Factory와 차이가 뭔가요?" → Factory Method는 하나의 메서드로 한 종류의 객체를 만들고, Abstract Factory는 연관된 객체 묶음(예: UI 테마의 버튼/체크박스/스크롤바)을 한 번에 만들어요.

  • "OCP 원칙과 어떻게 연결되나요?" → 새 결제 수단(TossPay)을 추가할 때 클라이언트 코드는 수정하지 않고 Factory 내부와 새 클래스만 추가해서 확장에 열려 있고 변경에 닫혀 있게 만들 수 있어요[2].


3. Strategy — 알고리즘을 갈아끼우기

한 줄 정의: 알고리즘 패밀리를 정의하고 각각을 별도 클래스에 캡슐화해서, 런타임에 서로 교체할 수 있게 만드는 행동 패턴이에요[3].

언제 쓰나요? "회원 등급별 할인 정책", "결제 수단별 수수료 계산"처럼 하나의 일을 여러 방식으로 처리해야 하고, 그 방식이 자주 바뀌거나 추가될 때 써요. Refactoring.guru도 "거대한 조건문으로 알고리즘을 분기하는 코드"가 Strategy 적용 1순위 신호라고 설명해요[3].

Java 예시 (할인 정책):

```java public interface DiscountPolicy { long discount(long price); }

public class NewMemberDiscount implements DiscountPolicy { public long discount(long price) { return price * 10 / 100; } }

public class VipMemberDiscount implements DiscountPolicy { public long discount(long price) { return price * 30 / 100; } }

public class OrderService { private final DiscountPolicy policy; public OrderService(DiscountPolicy policy) { this.policy = policy; }

public long finalPrice(long price) { return price - policy.discount(price); } } ```

면접 follow-up:

  • "State 패턴과 뭐가 다른가요?" → 구조는 비슷하지만 의도가 달라요. Strategy는 "어떻게 계산할지"(알고리즘) 를 바꾸고, State는 "지금 객체가 어떤 상태인지"(상태별 동작)를 모델링해요.

  • "Spring에서 어떻게 구현하나요?" → 인터페이스와 여러 구현체를 빈으로 등록한 뒤, Map로 주입받아 키로 골라 쓰는 방식이 흔해요. Spring이 자동으로 모든 구현체를 모아 주거든요.


4. Observer — 변화 알림 구독

한 줄 정의: 한 객체의 상태 변화를 구독한 여러 객체에게 자동으로 통지하는 행동 패턴이에요[4].

언제 쓰나요? "주문이 완료되면 → 알림 발송 + 재고 차감 + 통계 적재"처럼, 한 사건에 여러 후속 동작이 따라오고 그 동작들끼리는 서로 몰라도 될 때 써요.

Java 백엔드에서는 Spring의 ApplicationEventPublisher가 대표적인 Observer 구현이에요. 공식 Javadoc은 이 인터페이스를 "이벤트 발행 기능을 캡슐화한 인터페이스"로 정의해요[8].

Spring 예시:

```java // 1) 이벤트 정의 public record OrderCompletedEvent(Long orderId) {}

// 2) Publisher @Service @RequiredArgsConstructor public class OrderService { private final ApplicationEventPublisher publisher;

public void complete(Long orderId) { // ... 주문 완료 처리 ... publisher.publishEvent(new OrderCompletedEvent(orderId)); } }

// 3) Subscriber @Component public class NotificationListener { @EventListener public void on(OrderCompletedEvent event) { // 알림 발송 } } ```

면접 follow-up:

  • "Pub/Sub과 같은 건가요?" → 개념은 같은 가족이지만, 일반적으로 Observer는 같은 프로세스 안의 인메모리 통지, Pub/Sub은 메시지 브로커를 끼고 프로세스/서비스를 넘나드는 통지로 구분해요.

  • "Spring 이벤트는 동기인가요, 비동기인가요?"기본은 동기예요. 동기인 덕분에 listener가 publisher의 트랜잭션 컨텍스트에 참여할 수 있어요[9]. 비동기로 처리하려면 @Async를 추가하면 돼요.


5. Proxy — 대리자가 가로채기

한 줄 정의: 원본 객체로의 접근을 제어하는 대리자(substitute)를 두어, 호출 전후에 추가 작업을 끼워 넣을 수 있게 하는 구조 패턴이에요[5].

언제 쓰나요? 접근 제어, 로깅, 캐싱, 트랜잭션처럼 본 로직을 건드리지 않고 부가 기능을 얹고 싶을 때 써요. Spring을 쓴다면 이미 매일 쓰고 있어요 — @Transactional이 바로 Proxy 패턴의 가장 유명한 사례거든요[10].

@Transactional 동작 원리 (Spring 공식 블로그 기준):

``java @Service public class UserService { @Transactional public void register(User user) { userRepository.save(user); } } ``

겉보기엔 평범한 메서드 호출이지만, 실제로는 Spring이 UserService를 감싼 proxy 객체를 빈으로 등록해 둬요[10]. 클라이언트가 register()를 부르면 proxy가 먼저 트랜잭션을 시작하고, 본 메서드를 호출하고, 결과에 따라 커밋/롤백을 처리한 뒤 반환해요.

면접 follow-up:

  • "JDK Dynamic Proxy와 CGLIB은 언제 어떤 게 쓰이나요?" → 공식 문서에 따르면 target이 인터페이스를 구현하면 JDK Dynamic Proxy, 인터페이스가 없으면 CGLIB(서브클래스를 런타임에 생성)이 쓰여요[7]. CGLIB은 final 클래스/메서드는 프록시할 수 없어요.

  • "@Transactional이 적용되지 않는 경우를 본 적 있나요?" → 같은 클래스 안에서 this.method()로 직접 호출하면 proxy를 거치지 않기 때문에 트랜잭션이 적용되지 않아요. 이걸 self-invocation 문제라고 불러요[7]. 해결은 별도 빈으로 분리하거나 self를 주입받는 방법이 있어요.


면접에서 패턴 답변, 어떻게 풀어가야 할까요?

"Strategy 패턴 설명해 주세요"라는 질문을 받았을 때, 정의만 외워서 답하면 점수가 높지 않아요. 다음 4단계 프레임으로 답변해 보세요.

단계

내용

1. 정의

"알고리즘을 캡슐화해서 런타임에 교체 가능하게 만드는 패턴이에요."

2. 사용 시점

"회원 등급별 할인 같은 정책 분기를 if-else로 늘어놓는 코드를 발견했을 때 적용해요."

3. 트레이드오프

"분기가 사라지지만 클래스 수가 늘어나서, 전략이 2~3개로 끝날 거면 오히려 과한 추상화가 될 수 있어요."

4. 실무 예시

"최근 프로젝트에서 결제 수수료 계산을 Strategy로 분리해 PG사 추가 시 클라이언트 코드 수정 없이 확장했어요."

핵심은 "왜 그 상황에 그 패턴을 골랐는지"를 본인의 언어로 설명하는 거예요. 패턴 이름 암기보다 한 단계 위의 답이 되거든요.


마무리

5개 패턴을 한 줄씩 다시 정리해 볼게요.

패턴

카테고리

한 줄 요약

Singleton

생성

인스턴스를 단 하나로 보장

Factory Method

생성

객체 생성을 서브클래스에 위임

Strategy

행동

알고리즘을 캡슐화해 교체 가능

Observer

행동

상태 변화를 구독자에게 통지

Proxy

구조

대리자가 접근을 제어

패턴을 머리로만 이해하는 것과 면접에서 자기 입으로 설명할 수 있는 건 다른 일이에요. 실제로 답변해 보면 의외로 막히는 부분이 많거든요. 트리업의 모의 면접 기능에서 Spring/Java 백엔드 질문을 골라 한 패턴씩 말로 답해보면, 어디서 막히는지 빠르게 확인할 수 있어요.

학습한 패턴은 트리업의 스킬 관리 에 보유 스킬로 등록해 두면, 이력서나 면접 자기소개서에서 객체지향 역량을 정리할 때 그대로 쓸 수 있어요.

작은 노력들이 모여 큰 성장을 만들어요. 5개 패턴부터 천천히, 본인의 언어로 다져 보세요!


Footnotes

Java
백엔드
디자인 패턴
면접 준비
주니어 개발자
객체지향
Spring
Kotlin
Updated 2026.05.10

Recommended for you

  • 기획자에서 PM으로 전환하기 - 도메인 지식을 무기로 만드는 5단계
    Career
    서비스 기획자에서 PM·PO로 전환하려면 무엇이 필요할까요? 한국 IT 채용 공고와 토스·쿠팡 사례로 본 기획자의 무기 3가지, 채워야 할 갭 3가지, 이력서·면접 재프레이밍까지 한 번에 정리했어요.
  • AI 시대 살아남는 주니어 개발자 - 대체되지 않는 5가지 역량
    Career
    Copilot이 코드를 짜주는 시대, 주니어 개발자가 진짜 키워야 할 다섯 가지 역량을 Stack Overflow와 McKinsey 데이터로 정리했어요. 문제 정의, 시스템 설계, 코드 리뷰 깊이, 도메인 이해, 협업까지 면접·이력서에 바로 쓰는 가이드예요.
  • 이직 타이밍 점검 체크리스트 — 지금 떠나야 할까, 더 버텨야 할까
    Career
    직장인 69.5%가 이직을 고민 중이에요. 떠나야 할 신호와 머물러야 할 신호, 시장 조건, 10문항 자가진단, 의사결정 매트릭스까지. 사직서 내기 전 5분 안에 본인 위치를 점검할 수 있는 이직 타이밍 체크리스트를 정리했어요.
  • 데이터 분석가가 매일 쓰는 SQL 함수 TOP 15 — 실무에서 검증된 쿼리 모음
    Skill Roadmap
    주니어 데이터 분석가가 매일 손이 가는 SQL 함수 15개를 모았어요. 집계, 조건 분기, 날짜·시간, 윈도우 함수, 문자열, JOIN까지 — 각 함수의 용도, 실무 예시 SQL, 자주 하는 실수를 PostgreSQL·MySQL 기준으로 정리했어요.
  • 인턴 vs 개인 프로젝트 vs 자격증, 대학생이 집중해야 할 것은?
    Career
    대학생 취업 준비, 인턴·개인 프로젝트·자격증 중 무엇부터 해야 할까요? 경총·사람인·캐치 데이터로 채용 시장이 보는 진짜 평가 요소와 직무별 우선순위 프레임을 정리했어요.
  • PM 연봉 가이드 - 연차별 시장 데이터와 협상 팁
    Career
    잡코리아·원티드·사업보고서 데이터를 종합한 PM/PO 연차별 평균과 네카라쿠배 빅테크 평균, 협상에 바로 쓰는 5가지 팁까지 한 번에 정리했어요.