# 소프트웨어 디자인 패턴 참조 가이드 디자인 패턴은 반복적인 문제를 해결하는 검증된 방법론입니다. 코드 작성 전, 아래 **빠른 선택 가이드**로 패턴을 먼저 고르세요. --- ## 빠른 선택 가이드 | 상황 | 적합한 패턴 | 분류 | |------|------------|------| | 인스턴스를 하나만 유지해야 할 때 | 싱글톤 | 생성 | | 객체 생성이 복잡하거나 조건부일 때 | 팩토리 | 생성 | | 매개변수가 많거나 선택적인 객체 생성 | 빌더 | 생성 | | 기존 객체를 복제해서 확장할 때 | 프로토타입 | 생성 | | 복잡한 API를 단순하게 감싸야 할 때 | 퍼사드 | 구조 | | 호환되지 않는 인터페이스를 연결할 때 | 어댑터 | 구조 | | 접근 제어·캐싱·지연 로딩이 필요할 때 | 프록시 | 구조 | | 상태 변화를 여러 곳에 알려야 할 때 | 옵저버 | 행위 | | 컬렉션을 표준 방식으로 순회할 때 | 이터레이터 | 행위 | | 알고리즘을 런타임에 교체해야 할 때 | 전략 | 행위 | | 복잡한 조건문(if/switch)을 제거할 때 | 스테이트 | 행위 | | 객체 간 직접 통신을 줄이고 싶을 때 | 메디에이터 | 행위 | --- ## 1. 생성 패턴 (Creational Patterns) 객체를 **어떻게 만들지**를 다루는 패턴 ### 싱글톤 (Singleton) - **목적**: 클래스 인스턴스를 전역에서 단 하나만 보장 - **사용 예**: 앱 설정 객체, 전역 로거, 캐시 관리자 - **특징**: 어디서 접근해도 동일한 인스턴스 반환 ### 팩토리 (Factory) - **목적**: 객체 생성 로직을 캡슐화, 클라이언트에서 `new`를 직접 사용하지 않음 - **사용 예**: 플랫폼별 UI 컴포넌트 생성, 버거 주문 시스템 - **특징**: 생성할 구체 클래스를 호출자로부터 분리 ### 빌더 (Builder) - **목적**: 복잡한 객체를 단계별로 조립 - **사용 예**: SQL 쿼리 빌더, HTTP 요청 객체, 설정이 많은 DTO - **특징**: 메서드 체이닝 → `build()`로 최종 객체 완성 ### 프로토타입 (Prototype) - **목적**: 기존 객체를 복제(clone)하여 새 객체 생성 - **사용 예**: JavaScript 프로토타입 상속, 깊은 복사가 필요한 객체 - **특징**: 클래스 상속 없이 기능 확장 가능 --- ## 2. 구조 패턴 (Structural Patterns) 객체와 클래스를 **어떻게 조합할지**를 다루는 패턴 ### 퍼사드 (Facade) - **목적**: 복잡한 내부 시스템을 단순한 인터페이스로 노출 - **사용 예**: 외부 API 래퍼, 복잡한 라이브러리의 진입점 - **특징**: 내부 구현 세부사항을 숨기고 고수준 API 제공 ### 어댑터 (Adapter) - **목적**: 호환되지 않는 두 인터페이스를 연결 - **사용 예**: 레거시 코드 통합, 서드파티 라이브러리 래핑 - **특징**: 기존 클래스를 수정하지 않고 새 인터페이스에 맞춤 ### 프록시 (Proxy) - **목적**: 실제 객체 앞에 대리 객체를 두어 접근 제어·부가 기능 추가 - **사용 예**: Vue.js 반응형 시스템, 지연 로딩(Lazy Loading), API 캐싱 - **특징**: 실제 객체 교체 없이 동작 가로채기 가능 --- ## 3. 행위 패턴 (Behavioral Patterns) 객체 간 **통신과 책임 분배**를 다루는 패턴 ### 옵저버 (Observer / Pub-Sub) - **목적**: 한 객체의 상태 변화를 구독자들에게 자동으로 알림 - **사용 예**: Firebase 실시간 업데이트, 유튜브 구독 알림, 이벤트 버스 - **특징**: 1:N 관계, 발행자와 구독자의 느슨한 결합 ### 이터레이터 (Iterator) - **목적**: 컬렉션 내부 구조 노출 없이 요소를 순회 - **사용 예**: 배열·연결 리스트·트리의 통일된 순회 인터페이스 - **특징**: 자료구조 구현과 순회 로직을 분리 ### 전략 (Strategy) - **목적**: 알고리즘을 외부에서 주입받아 런타임에 교체 가능하게 함 - **사용 예**: 정렬 알고리즘 선택, 결제 수단 선택, 압축 방식 선택 - **특징**: Open-Closed 원칙 준수, 기존 코드 수정 없이 동작 확장 ### 메디에이터 (Mediator) - **목적**: 객체들이 서로 직접 통신하지 않고 중재자를 통해 소통 - **사용 예**: Express.js 미들웨어 체인, 채팅방 서버, 항공 관제 시스템 - **특징**: 객체 간 결합도 감소, 관계가 M:N → 1:N으로 단순화 ### 스테이트 (State) - **목적**: 객체 내부 상태에 따라 동작이 완전히 달라지게 분리 - **사용 예**: 게임 캐릭터 상태 머신, 주문 처리 흐름, UI 컴포넌트 모드 - **특징**: 복잡한 `if/switch` 조건문을 상태 클래스로 대체 --- ## 모듈 설계 원칙: 단순하고 테스트하기 쉽게 > 좋은 코드는 읽기 쉽고, 좋은 모듈은 테스트하기 쉽습니다. > 복잡한 패턴보다 **작고 명확한 단위**로 나누는 것이 먼저입니다. ### 단순하게 만들기 - **하나의 모듈 = 하나의 책임**: 파일 하나가 하는 일을 한 문장으로 설명할 수 없다면 쪼개야 함 - **의존성은 주입받기**: 모듈 안에서 직접 `new` 하거나 전역 참조 대신, 필요한 객체는 인자로 받기 - **외부와의 접점 최소화**: 공개 함수(export)는 꼭 필요한 것만, 나머지는 내부에 숨기기 - **설정과 로직 분리**: 환경변수·상수는 별도 파일로, 핵심 로직 안에 하드코딩 금지 ### 테스트하기 쉽게 만들기 - **순수 함수 우선**: 같은 입력 → 항상 같은 출력, 부작용 없는 함수는 테스트가 단 한 줄 - **I/O는 가장자리로**: DB·파일·HTTP 호출은 모듈 경계 바깥으로 밀어내고, 핵심 로직은 순수하게 유지 - **인터페이스 기반 설계**: 구체 구현 대신 인터페이스(프로토콜)에 의존하면 테스트 시 Mock으로 쉽게 교체 가능 - **작은 단위로 분리**: 함수 하나가 20줄을 넘기 시작하면 쪼갤 신호 ### 체크리스트 테스트 작성 전, 아래 질문에 "예" 가 되도록 설계하세요. | 질문 | 목표 | |------|------| | 이 함수를 Obsidian·Jenkins 없이 단독 실행할 수 있는가? | 외부 의존성 격리 | | Mock 없이 입력값만 바꿔 결과를 검증할 수 있는가? | 순수 함수화 | | 이 모듈의 역할을 한 문장으로 말할 수 있는가? | 단일 책임 | | 추가 기능이 생겼을 때 기존 코드를 수정하지 않아도 되는가? | 확장 가능성 | --- ## 주의사항 > 패턴은 도구이지 목적이 아닙니다. 단순한 문제에 복잡한 패턴을 적용하면 > 오히려 코드 가독성과 유지보수성이 떨어집니다. - **과도한 패턴 남용 금지**: 문제가 패턴을 정당화할 만큼 복잡한지 먼저 판단 - **성능 고려**: 프록시·옵저버 등 일부 패턴은 런타임 오버헤드 발생 가능 - **팀 이해도 우선**: 팀원 모두가 이해할 수 있는 수준의 패턴을 선택