Notice
Recent Posts
Recent Comments
Link
250x250
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
Tags
- issue
- 파이썬
- AndroidStudio
- 알고리즘
- cos pro 1급
- Flutter
- BAEKJOON
- 안드로이드스튜디오
- codingtest
- DART
- k8s
- cos
- 백준
- 동적계획법과최단거리역추적
- 개발
- vuejs
- Algorithm
- cos pro
- 코딩테스트
- 안드로이드
- 분할정복
- C++
- django
- 코드품앗이
- android
- Python
- 동적계획법
- 코테
- DFS
- DFS와BFS
Archives
- Today
- Total
Development Artist
GoF 디자인 패턴 총정리 본문
728x90
총 23개 패턴: GoF 디자인 패턴
1. 전략 패턴 (Strategy)
- 개요: 전략 패턴은 알고리즘을 하나의 객체로 캡슐화하여 실행 중에 동적으로 교체할 수 있도록 해주는 패턴이다.
동일한 기능을 여러 방식으로 구현해야 할 때 유용하며, 클라이언트 코드와 알고리즘 구현을 분리해 유연성을 높인다. - 원리: 공통 동작을 정의하는 인터페이스(또는 추상 클래스)를 만들고, 다양한 알고리즘을 각각의 전략(Strategy) 클래스로 구현한다.
클라이언트는 이 전략 객체를 주입받아 필요에 따라 알고리즘을 선택하거나 변경할 수 있다. - 장점: 알고리즘을 독립적으로 정의하고 교체할 수 있어 코드 재사용성과 유지보수성이 뛰어나다.
새로운 전략을 추가해도 기존 코드를 수정하지 않으므로 OCP(개방-폐쇄 원칙)를 충족시킨다. - 단점: 전략마다 클래스를 따로 만들어야 하므로 클래스 수가 증가하고 구조가 복잡해질 수 있다.
클라이언트가 어떤 전략을 선택해야 할지에 대한 책임이 생기며, 전략 간의 차이를 잘 이해해야 한다. - 예시 시스템
- 다양한 정렬 방식을 선택할 수 있는 정렬 유틸리티
- 결제 수단(카드, 포인트, 간편결제 등)을 전략으로 정의한 결제 시스템
- 게임 캐릭터의 행동(공격, 이동, 점프 등)을 전략으로 구현하여 동적으로 변경 가능한 게임 엔진
- 실무 활용도: ⭐⭐⭐⭐⭐
- 구현 난이도: ⭐⭐
2. 옵저버 패턴 (Observer)
- 개요: 옵저버 패턴은 한 객체의 상태 변화가 있을 때, 그 객체에 의존하는 다른 객체들에게 자동으로 알림을 보내는 패턴이다.
주로 데이터의 변경을 UI나 다른 시스템에 실시간으로 반영해야 할 때 유용하게 쓰인다. - 원리: 주체(Subject)는 관찰자(Observer)를 등록하고, 자신의 상태가 변경되면 등록된 모든 관찰자에게 알림을 보낸다.
관찰자는 주체의 변화를 감지하여 자신만의 방식으로 동작을 수행하며, 이 메커니즘은 느슨한 결합을 가능하게 한다. - 장점: 주체와 관찰자가 강하게 연결되지 않아 서로 독립적으로 변경이 가능하며, 실시간 동기화가 자연스럽게 이루어진다.
새로운 관찰자를 쉽게 추가할 수 있고, 주체의 내부 구현을 수정하지 않아도 된다. - 단점: 객체 간 의존 관계가 많아지면 흐름을 추적하기 어렵고, 디버깅이 복잡해질 수 있다.
순환참조나 메모리 누수 등의 이슈가 발생할 수 있으며, 이벤트 순서에 민감한 시스템에서는 주의가 필요하다. - 예시 시스템
- GUI 프레임워크에서 버튼 클릭 이벤트를 처리하는 리스너
- 실시간 뉴스/주식 구독 시스템
- IoT 센서 네트워크에서 상태 변화를 모니터링하는 알림 시스템
- 실무 활용도: ⭐⭐⭐⭐
- 구현 난이도: ⭐⭐
3. 데코레이터 패턴 (Decorator)
- 개요: 데코레이터 패턴은 기존 객체의 구조를 변경하지 않고, 런타임에 새로운 기능을 동적으로 추가할 수 있도록 하는 구조 패턴이다.
상속 대신 객체를 감싸는 방식(Composition)을 통해 기능을 확장할 수 있어 매우 유연한 확장성을 제공한다. - 원리: 핵심 객체와 동일한 인터페이스를 갖는 데코레이터 클래스가 실제 객체를 내부에 포함(wrap)하고, 기존 기능을 유지하면서 새로운 동작을 추가한다.
여러 개의 데코레이터를 중첩해 조합할 수 있어 기능의 조립이 자유롭다. - 장점: 상속보다 더 유연하게 기능을 확장할 수 있고, 기능 단위를 모듈화하여 재사용과 조합이 가능하다.
객체 간의 관계를 느슨하게 유지하면서, 필요한 기능만 선택적으로 부여할 수 있다. - 단점: 데코레이터 클래스가 많아져 구조가 복잡해지고, 디버깅이 어려워질 수 있다.
중첩된 구조가 깊어지면 디버깅이나 문제 추적이 불편해질 수 있으며, 생성 순서가 중요한 경우도 있다. - 예시 시스템
- 자바의 InputStream, BufferedInputStream, GZIPInputStream 등 입출력 스트림 처리
- UI 컴포넌트에 스크롤바, 테두리, 그림자 등을 레이어처럼 추가하는 GUI 시스템
- 로그 출력 포맷터 또는 메시지 암호화/압축 기능의 조합
- 실무 활용도: ⭐⭐⭐⭐
- 구현 난이도: ⭐⭐⭐
4. 팩토리 패턴 (Factory)
- 개요: 팩토리 패턴은 객체 생성 코드를 클라이언트 코드로부터 분리하여, 객체 생성을 전문화된 클래스(또는 메서드)에 위임하는 생성 패턴이다.
객체 생성 방식을 캡슐화함으로써, 클라이언트는 구체적인 클래스에 의존하지 않고 유연하게 인스턴스를 생성할 수 있다. - 원리: 생성할 객체의 타입을 결정하는 로직을 팩토리 메서드나 클래스 안에 정의하고, 클라이언트는 이를 호출해 객체를 얻는다.
서브클래스가 팩토리 메서드를 오버라이드하여 객체 생성 방식을 다양화할 수 있으며, 이를 통해 코드의 유연성을 확보한다. - 장점: 객체 생성 코드를 캡슐화하여 책임을 분리할 수 있고, 생성 로직이 변경되어도 클라이언트 코드는 변경되지 않는다.
새로운 제품군이 추가되더라도 팩토리만 수정하면 되므로 OCP(개방-폐쇄 원칙)을 지킬 수 있다. - 단점: 객체 생성 과정을 간접화하면서 코드 추적이 어려워질 수 있고, 클래스 수가 많아져 구조가 복잡해질 수 있다.
단순한 경우에는 오히려 과설계가 될 수 있어 사용 시 균형이 필요하다. - 예시 시스템
- 피자 가게에서 지역 스타일에 따라 피자 객체를 다르게 생성하는 PizzaStore
- GUI 프레임워크에서 버튼, 텍스트필드 등을 플랫폼에 따라 다르게 생성
- JDBC에서 드라이버별 커넥션을 생성하는 ConnectionFactory
- 실무 활용도: ⭐⭐⭐⭐⭐
- 구현 난이도: ⭐⭐
5. 싱글턴 패턴 (Singleton)
- 개요:
싱글턴 패턴은 애플리케이션 전체에서 단 하나의 인스턴스만 생성되도록 보장하는 생성 패턴이다.
전역적으로 동일한 인스턴스에 접근해야 하는 경우에 주로 사용되며, 인스턴스 공유를 통해 일관성을 유지할 수 있다. - 원리:
생성자를 private으로 막고, 정적(static) 메서드를 통해 유일한 인스턴스를 생성 및 반환한다.
인스턴스는 클래스 내에 정적으로 보관되며, 필요할 때마다 동일한 객체를 반환한다. - 장점:
시스템 전체에서 하나의 인스턴스를 공유함으로써 리소스를 절약하고, 설정 정보나 로그 등의 상태를 일관되게 유지할 수 있다.
글로벌 접근 지점(Global Access Point)을 제공하므로 접근이 편리하다. - 단점:
테스트 시 글로벌 상태가 테스트 간 영향을 줄 수 있어 단위 테스트가 어려워지고, 다중 스레드 환경에서는 동기화 이슈가 발생할 수 있다.
또한 잘못 사용하면 전역 변수처럼 남용되어 시스템의 결합도를 높일 수 있다. - 예시 시스템
- 설정 정보를 전역으로 관리하는 ConfigurationManager
- 로그 출력을 전역에서 처리하는 Logger
- 캐시 메모리 관리나 연결 풀(커넥션 풀)과 같은 리소스 공유 객체
- 실무 활용도: ⭐⭐⭐⭐⭐
- 구현 난이도: ⭐
6. 커맨드 패턴 (Command)
- 개요:
커맨드 패턴은 요청을 하나의 객체로 캡슐화하여, 요청을 큐잉하거나 저장하거나 실행 취소할 수 있도록 만드는 행동 패턴이다.
실행되는 동작을 직접 호출하지 않고, 명령 객체를 통해 요청을 추상화함으로써 유연한 커맨드 실행 구조를 만든다. - 원리:
명령(Command) 객체는 실행될 동작을 알고 있는 리시버(Receiver)를 포함하고 있으며, execute() 메서드를 통해 리시버의 행동을 호출한다.
클라이언트는 명령 객체를 인보커(Invoker)에 전달하고, 인보커는 명령을 실행하거나 저장, 취소하는 등의 기능을 수행한다. - 장점:
요청을 객체로 다루기 때문에 Undo/Redo, 매크로 실행, 커맨드 로그 등 다양한 기능을 쉽게 구현할 수 있다.
리시버와 실행 로직이 분리되어 있어 코드 변경 없이 새로운 명령을 추가할 수 있고, OCP(개방-폐쇄 원칙)를 만족한다. - 단점: 명령 객체가 기능별로 필요하므로 클래스 수가 많아져 구조가 복잡해질 수 있다.
단순한 요청을 처리할 때는 오히려 과도한 설계(오버엔지니어링)가 될 수 있다. - 예시 시스템
- 리모컨처럼 버튼에 따라 명령을 바인딩하는 전자제품 제어 시스템
- 텍스트 편집기나 그래픽 툴에서의 Undo/Redo 기능
- 게임에서의 매크로 커맨드 실행 또는 명령 스택 관리
- 실무 활용도: ⭐⭐⭐
- 구현 난이도: ⭐⭐⭐
7. 어댑터 패턴 (Adapter)
- 개요:
어댑터 패턴은 기존 클래스의 인터페이스를 클라이언트가 기대하는 인터페이스에 맞게 변환해주는 구조 패턴이다.
서로 호환되지 않는 인터페이스를 연결해주는 중간 계층 역할을 하며, 레거시 코드와의 통합이나 라이브러리 연동 시 자주 사용된다. - 원리:
클라이언트가 기대하는 인터페이스와 다른 인터페이스를 가진 클래스를 감싸(wrap)서, 클라이언트가 원하는 메서드 시그니처로 변환해준다.
어댑터는 대상 객체를 내부에 포함하거나(객체 어댑터), 그 객체를 상속(클래스 어댑터)하여 동작을 변환한다. - 장점:
기존 코드를 수정하지 않고도 재사용할 수 있으며, 시스템 간의 호환성을 높인다.
외부 라이브러리, 서드파티 API, 구형 모듈 등을 현대적인 시스템에 통합하는 데 매우 유용하다. - 단점:
어댑터 계층이 늘어나면서 구조가 복잡해질 수 있고, 코드 추적이 어려울 수 있다.
너무 많은 어댑터가 생기면 전체 시스템의 유지보수가 어려워질 수 있다. - 예시 시스템
- 구형 API를 신형 API로 감싸는 래퍼 클래스 (예: LegacyUserServiceAdapter)
- Java에서 Enumeration을 Iterator로 변환하는 EnumerationIterator
- 결제 시스템에서 PayPal, KakaoPay 등 서로 다른 인터페이스를 PaymentGateway 인터페이스로 통일해 처리하는 어댑터
- 실무 활용도: ⭐⭐⭐⭐
- 구현 난이도: ⭐⭐
8. 퍼사드 패턴 (Facade)
- 개요:
퍼사드 패턴은 복잡한 내부 시스템을 하나의 단순한 인터페이스로 감싸, 클라이언트가 쉽게 사용할 수 있도록 도와주는 구조 패턴이다.
여러 객체와 상호작용해야 하는 복잡한 로직을 감추고, 하나의 진입점(Facade)을 제공하여 사용성을 높인다. - 원리:
여러 컴포넌트(서브시스템)에 대한 복잡한 호출 순서나 의존성을 퍼사드 클래스에 숨긴다.
클라이언트는 퍼사드를 통해 내부 구조를 몰라도 되고, 퍼사드는 내부 컴포넌트에 적절히 요청을 위임하여 처리한다. - 장점:
클라이언트와 서브시스템 간의 결합도를 낮추고, 변경에 강한 유연한 구조를 만들 수 있다.
복잡한 하위 시스템을 감춰 사용하기 쉽게 만들어주며, 서브시스템은 그대로 두고 클라이언트만 단순화할 수 있다. - 단점:
퍼사드가 너무 많은 책임을 가지게 되면 God Object가 될 수 있다.
모든 기능을 퍼사드에 노출하기 어렵거나, 퍼사드를 너무 많이 만들면 오히려 복잡성이 증가할 수 있다. - 예시 시스템
- 홈시어터 시스템에서 DVD, 조명, 프로젝터 등을 한 번에 켜고 끄는 HomeTheaterFacade
- 빌드 툴(예: Gradle, Maven)에서 수많은 태스크를 간단한 명령어로 실행하는 인터페이스
- Spring에서 여러 Bean 호출을 내부에 감추고 @Service 하나로 기능을 노출하는 퍼사드 서비스 계층
- 실무 활용도: ⭐⭐⭐⭐
- 구현 난이도: ⭐⭐
9. 템플릿 메소드 패턴 (Template Method)
- 개요:
템플릿 메소드 패턴은 알고리즘의 구조(순서)는 고정하되, 일부 단계는 하위 클래스에서 구현하도록 유도하는 행동 패턴이다.
전체적인 흐름을 슈퍼클래스에서 제어하고, 구체적인 세부사항은 서브클래스가 오버라이딩하여 커스터마이징할 수 있다. - 원리:
상위 클래스에 templateMethod()를 정의하고, 그 내부에 알고리즘의 각 단계를 메서드 호출로 표현한다.
이 중 일부 메서드는 추상 메서드나 Hook 메서드로 선언하여 하위 클래스가 구체적으로 구현할 수 있도록 한다. - 장점:
알고리즘의 중복을 제거하고 코드 재사용을 극대화할 수 있으며, 전체적인 흐름을 변경하지 않고도 세부 동작을 유연하게 조정할 수 있다.
프레임워크나 라이브러리의 훅 메서드를 제공하는 구조와도 잘 어울린다. - 단점:
상속에 의존하므로 유연성이 떨어지고, 하위 클래스가 상위 클래스의 흐름을 충분히 이해하고 있어야 한다.
일부 개발자에게는 제어권이 역전된(Inversion of Control) 느낌이 어색할 수 있다. - 예시 시스템
- 커피와 차를 끓이는 과정을 공통화한 CaffeineBeverage 클래스 (물 끓이기 → 우려내기 → 따르기)
- 게임에서 턴 처리 순서를 정한 GameTurnManager에서 executeTurn()만 각 캐릭터가 오버라이드
- Spring, Django 등의 프레임워크에서 확장 포인트로 제공하는 추상 클래스와 doXXX() 메서드 구조
- 실무 활용도: ⭐⭐⭐
- 구현 난이도: ⭐⭐
10. 반복자 패턴 (Iterator)
- 개요:
반복자 패턴은 컬렉션 객체의 내부 구조를 노출하지 않고도 그 요소들을 순차적으로 접근할 수 있게 해주는 행동 패턴이다.
for 문 같은 반복 로직을 컬렉션 외부에서 추상화할 수 있어, 구조에 상관없이 동일한 방식으로 순회할 수 있다. - 원리:
컬렉션 객체는 Iterator 인터페이스를 구현한 객체를 반환하고, 클라이언트는 hasNext()와 next()를 통해 순차적으로 요소에 접근한다.
컬렉션의 내부 구조(배열, 리스트, 트리 등)에 상관없이, 동일한 프로토콜로 접근할 수 있도록 설계된다. - 장점:
컬렉션의 구조를 숨긴 채로 순회할 수 있으므로 추상화 수준이 높고 유연하다.
순회 로직과 컬렉션 구현이 분리되어 있어 유지보수가 쉽고, 새로운 컬렉션 구조를 도입해도 클라이언트 코드는 변경되지 않는다. - 단점:
컬렉션을 순회하는 동안 구조가 변경되면 ConcurrentModificationException과 같은 예외가 발생할 수 있다.
상태가 있는 객체라 반복 중복이 어렵거나, 병렬 처리나 필터링 등 고급 작업이 제한적일 수 있다 (→ Java Stream이 이를 보완함). - 예시 시스템
- Java의 Iterator 인터페이스 (List.iterator(), Set.iterator() 등)
- 데이터베이스의 커서(cursor) 객체를 통해 결과 집합 순회
- 커스텀 컬렉션에서 사용자 정의 반복자(MyCollectionIterator) 구현
- 실무 활용도: ⭐⭐⭐⭐
- 구현 난이도: ⭐
11. 컴포지트 패턴 (Composite)
- 개요:
컴포지트 패턴은 객체들을 트리 구조로 구성하여, 단일 객체와 객체 그룹을 동일하게 처리할 수 있도록 해주는 구조 패턴이다.
클라이언트는 복잡한 계층 구조의 객체 집합(Composite)과 단일 객체(Leaf)를 구분하지 않고 동일한 방식으로 사용할 수 있다. - 원리:
Leaf와 Composite 객체 모두 동일한 인터페이스나 추상 클래스를 구현하여, 클라이언트는 일관된 API로 호출만 하면 된다.
Composite는 내부에 자식 컴포넌트를 가지고 있으며, 이들을 재귀적으로 순회하거나 실행할 수 있도록 구성된다. - 장점:
클라이언트 코드가 단순해지고, 객체 계층 구조를 유연하게 표현할 수 있다.
복잡한 트리 구조의 데이터를 계층적으로 관리하거나, UI, 파일시스템 등에서 매우 자연스럽게 적용된다. - 단점:
Leaf와 Composite 객체를 명확하게 구분하기 어려워질 수 있으며, 설계나 디버깅이 복잡해질 수 있다.
모든 구성 요소가 동일한 인터페이스를 강제받기 때문에, 때로는 불필요한 기능까지 구현해야 할 수도 있다. - 예시 시스템
- 파일 시스템에서 파일(Leaf)과 폴더(Composite)를 동일하게 처리
- HTML DOM 트리 구조에서 요소를 계층적으로 표현
- GUI 프레임워크에서 버튼, 패널, 레이아웃 등 컴포넌트를 트리 형태로 구성
- 메뉴 시스템: 메뉴 항목과 서브 메뉴를 하나의 인터페이스로 관리
- 실무 활용도: ⭐⭐⭐
- 구현 난이도: ⭐⭐⭐
12. 상태 패턴 (State)
- 개요:
상태 패턴은 객체의 내부 상태에 따라 행동이 달라지는 경우, 상태를 객체로 캡슐화하여 동작을 유연하게 제어하는 행동 패턴이다.
if나 switch 문으로 상태 분기하는 대신, 상태 객체 자체가 동작을 정의하도록 하여 코드의 가독성과 확장성을 높인다. - 원리:
각 상태를 클래스로 분리하고, 컨텍스트(Context)는 현재 상태 객체에게 동작을 위임한다.
상태 객체는 자신의 동작을 정의할 뿐 아니라, 다음 상태로의 전이 또한 스스로 결정할 수 있다 (상태 전이 로직 포함). - 장점:
조건문으로 상태를 분기하는 방식보다 훨씬 유지보수성과 확장성이 높고, 상태 전이 로직이 명확하게 분리된다.
새로운 상태를 추가할 때 기존 코드에 영향을 주지 않으므로 OCP(개방-폐쇄 원칙)을 잘 따른다. - 단점:
상태마다 클래스를 만들어야 하므로 클래스 수가 증가하고 구조가 복잡해질 수 있다.
단순한 상태 전이 로직에는 과한 설계가 될 수 있으며, 상태 객체 간의 전이 순서를 잘못 설계하면 오작동이 생길 수 있다. - 예시 시스템
- 자판기: 동전 없음, 동전 있음, 상품 판매 중, 품절 등의 상태를 클래스로 분리
- TCP 연결: CLOSED, LISTEN, SYN_SENT, ESTABLISHED 등의 상태 전이 모델 구현
- 게임 캐릭터: 일반 상태, 피격 상태, 무적 상태 등 상황에 따라 캐릭터의 행동이 달라짐
- 실무 활용도: ⭐⭐
- 구현 난이도: ⭐⭐⭐
13. 프록시 패턴 (Proxy)
- 개요:
프록시 패턴은 실제 객체에 대한 접근을 제어하기 위해 그 객체를 대신하는 대리 객체(Proxy)를 제공하는 구조 패턴이다.
클라이언트는 프록시를 통해 실제 객체와 동일한 방식으로 동작을 호출하지만, 프록시가 사전/사후 처리를 추가하거나 접근을 제한할 수 있다. - 원리:
프록시는 실제 객체(RealSubject)와 동일한 인터페이스를 구현하고, 내부에 실제 객체를 참조한 뒤 메서드를 위임하거나 제어 로직을 삽입한다.
원격 호출(Remote Proxy), 지연 초기화(Virtual Proxy), 접근 제어(Protection Proxy), 부가 처리(Smart Proxy) 등 다양한 형태로 사용된다. - 장점:
실제 객체를 숨기거나 보호할 수 있고, 객체 생성 비용이 큰 경우에는 프록시를 통해 지연 생성이 가능하다.
접근 제어, 로깅, 캐싱, 권한 확인, 트랜잭션 등 다양한 부가기능을 프록시에 위임하여 시스템의 관심사를 분리할 수 있다. - 단점:
프록시가 많아지면 시스템이 계층적으로 복잡해지고, 흐름을 추적하기 어려울 수 있다.
프록시와 실제 객체의 구분이 모호해지면 디버깅이나 유지보수가 어려워질 수 있으며, 성능 병목이 발생할 수 있다. - 예시 시스템
- Java RMI: 원격 객체를 로컬 프록시 객체로 감싸 호출하는 RemoteProxy
- 지연 이미지 로딩: 이미지 객체를 렌더링하기 전까지 프록시로 대체 (Virtual Proxy)
- Spring AOP: 메서드 실행 전/후에 트랜잭션, 로깅, 보안 등을 처리하는 동적 프록시 기반 인터셉터
- 실무 활용도: ⭐⭐⭐⭐
- 구현 난이도: ⭐⭐⭐
14. 복합 패턴 (Compound)
- 개요:
복합 패턴은 두 개 이상의 디자인 패턴을 조합하여 복잡한 문제를 유연하고 효과적으로 해결하는 아키텍처 패턴이다.
단일 패턴으로는 다루기 어려운 요구사항들을 여러 패턴을 조합함으로써 더 강력한 설계를 만들 수 있다. - 원리:
여러 디자인 패턴을 역할에 따라 조화롭게 결합하여 동작하게 한다. 각 패턴은 독립적으로 책임을 가지며, 상호 보완적으로 작동한다.
복합 패턴은 각 패턴의 강점을 결합하면서도 전체 설계를 모듈화하여 높은 응집도와 낮은 결합도를 유지한다. - 장점:
복잡한 시스템을 계층화하고, 각 역할을 명확하게 분리하여 유지보수성과 확장성이 높아진다.
여러 패턴의 조합을 통해 단일 패턴보다 유연하고 실용적인 아키텍처를 구성할 수 있다. - 단점:
여러 패턴이 동시에 작동하므로 설계 난이도와 학습 곡선이 높아질 수 있고, 전체 흐름을 파악하기 어려워질 수 있다.
잘못 조합하면 과도한 추상화 또는 오버엔지니어링이 될 수 있다. - 예시 시스템
- MVC 패턴: Model(Observer), View(Observer), Controller(Strategy) 역할의 조합
- Head First의 오리 시뮬레이터: Strategy, Decorator, Composite, Observer, Factory 등이 함께 사용됨
- Spring Framework 내부: IoC(Factory), AOP(Proxy), EventSystem(Observer) 등 다양한 패턴이 복합적으로 사용됨
- 실무 활용도: ⭐⭐⭐
- 구현 난이도: ⭐⭐⭐⭐
15. 브리지 패턴 (Bridge)
- 개요:
브리지 패턴은 구현부(Implementation)와 추상화(Abstraction)를 분리하여 각자 독립적으로 확장할 수 있도록 돕는 구조 패턴이다.
일반적으로 계층 구조가 복잡해질 때 상속 대신 조합(Composition)을 사용하여 유연성을 높이고 결합도를 낮춘다. - 원리:
추상화 계층(Abstraction)은 구현 계층(Implementor)에 대한 참조를 포함하며, 실행 시 구현 객체에 위임한다.
이로써 추상화와 구현이 독립적으로 확장되며, 서로 영향을 주지 않고 확장 가능한 구조를 만든다. - 장점:
클래스 계층이 여러 방향(기능, 플랫폼 등)으로 확장될 수 있는 경우에 상속의 폭발을 방지할 수 있다.
런타임에 구현체를 교체할 수 있어 매우 유연하고, 코드 재사용성과 테스트 용이성이 높다. - 단점:
계층이 늘어나기 때문에 설계가 복잡해질 수 있고, 초기에 구조를 이해하고 설정하는 데 비용이 든다.
단순한 경우에는 오히려 과도한 추상화가 될 수 있다. - 예시 시스템
- UI 라이브러리: 버튼이나 윈도우를 Windows, Linux, Mac 등에 맞춰 구현 계층만 바꾸면 같은 추상 인터페이스로 동작
- JDBC 드라이버: 인터페이스는 같지만 DB 종류에 따라 실제 구현이 다르게 동작함 (ex. MySQL, Oracle, PostgreSQL)
- 리포트 생성기: PDF, HTML, 엑셀 등 출력 포맷과 리포트 내용 구조를 분리하여 확장 가능
- 실무 활용도: ⭐⭐⭐
- 구현 난이도: ⭐⭐⭐⭐
16. 빌더 패턴 (Builder)
- 개요:
빌더 패턴은 복잡한 객체를 생성할 때, 생성 과정을 단계적으로 나누고 각 단계를 독립적으로 처리할 수 있도록 도와주는 생성 패턴이다.
특히 생성자에 매개변수가 너무 많거나, 선택적 필드가 많을 때 가독성과 유연성을 높이기 위해 널리 사용된다. - 원리:
빌더 객체는 필드를 설정하는 메서드를 체이닝 방식(return this)으로 구현하고, 마지막에 build() 메서드로 최종 객체를 반환한다.
불변 객체(Immutable Object)를 만들거나, 객체 생성을 분리하고 캡슐화하는 데 유리하다. - 장점:
코드 가독성이 높고, 점진적으로 구성할 수 있어 설정 유연성이 뛰어나다.
객체 생성을 캡슐화하여, 객체 생성 시 실수나 복잡성을 줄여준다. - 단점:
클래스를 추가로 정의해야 하므로, 간단한 객체에 쓰면 오히려 과설계가 될 수 있다.
특히 빌더 클래스가 많아지면 코드가 흩어지고 유지보수가 어려워질 수 있다. - 예시 시스템
- StringBuilder, StringBuffer: 문자열을 효율적으로 조합
- HTTP 클라이언트 요청 생성기 (예: OkHttp의 Request.Builder)
- 복잡한 설정 객체를 생성하는 도메인 DSL, 설정 파서
- 실무 활용도: ⭐⭐⭐⭐⭐
- 구현 난이도: ⭐⭐
17. 책임 연쇄 패턴 (Chain of Responsibility)
- 개요:
책임 연쇄 패턴은 요청을 처리할 수 있는 여러 객체를 연결된 체인 구조로 구성하여, 요청을 순차적으로 전달하고 처리할 수 있도록 하는 행동 패턴이다.
요청을 보낸 객체는 누가 처리하는지 몰라도 되며, 처리자가 알아서 결정되도록 흐름을 구성한다. - 원리:
각 처리 객체는 공통된 인터페이스를 구현하며, 요청을 처리하거나 다음 객체로 위임할 수 있다.
체인의 끝에 도달하거나 처리 가능한 객체가 요청을 담당하면서, 유연한 처리 흐름을 만든다. - 장점:
책임 처리자와 클라이언트를 분리하여 유연한 구조와 OCP 준수를 제공한다.
체인을 구성하거나 변경하는 것이 자유로워, 동적으로 로직 흐름을 구성할 수 있다. - 단점:
요청이 어디서 처리되었는지를 추적하기 어렵고, 체인이 너무 길거나 복잡하면 디버깅이나 흐름 분석이 어렵다.
각 노드가 처리하지 않고 단순 위임만 할 경우, 낭비가 발생할 수 있다. - 예시 시스템
- Servlet Filter Chain: 요청을 여러 필터를 통해 순차적으로 처리
- 로깅 시스템: 콘솔 → 파일 → 네트워크 순으로 로그를 전달
- UI 이벤트 처리: 이벤트가 가장 가까운 컴포넌트부터 시작해 상위로 전파됨
- 실무 활용도: ⭐⭐⭐⭐
- 구현 난이도: ⭐⭐⭐
18. 플라이웨이트 패턴 (Flyweight)
- 개요:
플라이웨이트 패턴은 대량의 객체를 효율적으로 관리하기 위해, 공유 가능한 인스턴스를 재사용함으로써 메모리 사용을 최소화하는 구조 패턴이다.
동일한 데이터를 가지는 객체를 중복 생성하지 않고 공유함으로써 리소스를 절약하는 데 주로 사용된다. - 원리:
객체의 상태를 내부 상태(공유 가능)와 외부 상태(개별적)로 분리한다.
내부 상태는 캐시된 객체로 재사용하고, 외부 상태는 호출 시 클라이언트가 주입하여 동작을 완성한다.
객체 풀(pool) 또는 팩토리를 통해 공유 인스턴스를 관리하는 방식으로 구현된다. - 장점:
동일한 데이터를 가진 객체 수를 줄이기 때문에 메모리 사용량을 획기적으로 절감할 수 있다.
특히 렌더링, 캐릭터 처리, 토큰 분할 등 반복 구조가 많은 대규모 시스템에서 성능에 큰 이점을 준다. - 단점:
내부/외부 상태를 구분하고, 객체의 재사용 여부를 판단하는 로직이 복잡해질 수 있다.
객체가 여러 스레드에서 공유되면 동기화(synchronization) 문제가 발생할 수 있어 주의가 필요하다. - 예시 시스템
- 글꼴 렌더링 시스템에서 각 문자 글리프(Glyph)를 공유하여 수천 개의 텍스트를 효율적으로 처리
- 게임 캐릭터에서 동일한 모델 객체를 공유하고 위치, 방향 등 외부 상태만 다르게 주입
- Java의 String 상수 풀 (intern() 메서드로 동일 문자열을 재사용)
- 실무 활용도: ⭐⭐
- 구현 난이도: ⭐⭐⭐⭐
19. 인터프리터 패턴 (Interpreter)
- 개요:
인터프리터 패턴은 언어의 문법 규칙을 객체로 표현하고, 해당 문장을 해석하는 방법을 클래스 기반으로 정의하는 행동 패턴이다.
주로 도메인 특화 언어(DSL)나 간단한 수식 언어를 해석하고 실행해야 할 때 사용된다. - 원리:
언어의 문법 규칙을 추상 구문 트리(Abstract Syntax Tree, AST)로 표현하고, 각 노드 클래스에 interpret() 메서드를 구현하여 재귀적으로 실행한다.
각 규칙(Non-terminal, Terminal)을 클래스로 분리하여, 재귀적인 구조로 문장을 해석하고 결과를 반환한다. - 장점:
언어를 클래스로 정의하므로 문법을 명확하게 표현할 수 있고, 문법을 손쉽게 확장하거나 수정할 수 있다.
새로운 연산자나 표현식을 추가할 때 OCP(개방-폐쇄 원칙)을 만족할 수 있다. - 단점:
문법의 각 요소마다 클래스를 생성해야 하므로 클래스 수가 급격히 증가하고, 복잡한 언어에는 적용이 매우 어렵다.
성능이 떨어지고 실행 속도가 느려질 수 있어, 대규모 파싱보다는 간단한 해석기 수준에 적합하다. - 예시 시스템
- 간단한 수식 계산기: "3 + (2 * 5)" 같은 표현을 파싱하고 해석
- SQL 파서: WHERE 절 조건식 해석 및 실행 (단순 필터 언어)
- 정규 표현식 엔진(Regex): 문자열 패턴을 해석하여 일치 여부 판단
- 템플릿 엔진의 조건문 또는 반복문 해석 (ex. Jinja2, Thymeleaf DSL)
- 실무 활용도: ⭐
- 구현 난이도: ⭐⭐⭐⭐⭐
20. 중재자 패턴 (Mediator)
- 개요:
중재자 패턴은 객체 간의 직접적인 상호작용을 제거하고, 중재자(Mediator) 객체를 통해 간접적으로 통신하게 만드는 행동 패턴이다.
복잡한 객체 간의 관계를 한 곳에서 통제하여, 클래스 간의 결합도를 낮추고 유지보수를 쉽게 만든다. - 원리:
여러 컴포넌트(객체)는 서로 직접 참조하지 않고, 공통된 중재자 인터페이스를 통해 메시지를 전달하거나 이벤트를 위임한다.
중재자는 각 객체의 상태를 알고 있으며, 메시지를 받아 적절한 컴포넌트에 전달하거나 동작을 트리거한다. - 장점:
컴포넌트 간의 직접 의존 관계가 제거되므로 결합도가 낮아지고, 새로운 컴포넌트를 쉽게 추가하거나 변경할 수 있다.
중앙 집중식 제어가 가능해 복잡한 UI나 이벤트 흐름을 단순화할 수 있으며, 코드 응집도가 높아진다. - 단점:
모든 의사소통이 중재자에게 집중되기 때문에, 중재자가 너무 많은 책임을 가지게 되어 비대해질 수 있다.
잘못 설계하면 God Object가 되어버릴 위험이 있으며, 전체 흐름이 중재자 내부에 감춰져 디버깅이 어려울 수 있다. - 예시 시스템
- 채팅 시스템: 클라이언트들은 서로 직접 통신하지 않고 채팅 서버(중재자)를 통해 메시지를 전달
- UI 폼 구성: 각 위젯(버튼, 텍스트박스 등)은 중재자에게 이벤트를 전달하고, 중재자가 상태를 관리
- 실무 활용도: ⭐⭐
- 구현 난이도: ⭐⭐⭐
21. 메멘토 패턴 (Memento)
- 개요:
메멘토 패턴은 객체의 상태를 이전 시점으로 복원할 수 있도록, 상태 정보를 외부에 노출하지 않고 캡슐화하여 저장하는 행동 패턴이다.
주로 Undo/Redo 기능이나 상태 저장 기능을 구현할 때 사용된다. - 원리:
상태를 저장하는 Memento 객체, 상태를 보존하고 복원하는 Originator, 그리고 상태를 저장·관리하는 Caretaker로 구성된다.
Memento는 Originator의 내부 상태를 캡슐화하여 저장하며, Caretaker는 그 메멘토 객체를 보관만 하고 내부 내용은 알지 못한다. - 장점:
객체의 내부 구현을 외부에 노출하지 않으면서도 이전 상태로 롤백할 수 있으므로, 캡슐화를 완전히 유지할 수 있다.
사용자의 실수 방지(Undo)나 시스템 복구 기능에 자연스럽게 활용된다. - 단점:
상태 저장 시 메모리를 많이 차지할 수 있고, 상태가 커질수록 성능 및 메모리 부담이 증가할 수 있다.
객체의 상태 변경이 자주 일어나거나 저장 주기가 짧은 경우, 메멘토 관리가 어려워질 수 있다. - 예시 시스템
- 텍스트 에디터의 Undo/Redo 기능: 입력 상태를 메멘토로 저장 후 되돌림
- 게임 저장 시스템: 플레이어 상태, 위치, 진행 상황 등을 저장 및 복원
- 워드 프로세서: 문서 편집 중간 저장 및 취소
- 실무 활용도: ⭐⭐
- 구현 난이도: ⭐⭐
22. 프로토타입 패턴 (Prototype)
- 개요:
프로토타입 패턴은 이미 존재하는 객체를 복제(clone)해서 새로운 객체를 생성하는 생성 패턴이다.
복잡한 초기화 과정을 피하고 빠르게 객체를 생성할 수 있으며, 런타임 시점에서 새로운 인스턴스를 만들 수 있다. - 원리:
복제 가능한 객체(Prototype)는 clone() 메서드를 통해 자신의 복제본을 생성할 수 있도록 정의한다.
이 복제는 얕은 복사(Shallow Copy) 또는 깊은 복사(Deep Copy)로 구현될 수 있으며, Java에서는 Cloneable 인터페이스를 활용하는 경우가 많다. - 장점:
초기화 비용이 높은 객체를 효율적으로 재사용할 수 있으며, 클래스에 대한 의존 없이도 객체를 생성할 수 있다.
복잡한 객체의 인스턴스를 빠르게 생성하고, 템플릿 기반의 복제가 가능하다. - 단점:
깊은 복사와 얕은 복사의 차이를 명확히 이해하고 관리하지 않으면 원치 않는 상태 공유나 버그가 발생할 수 있다.
객체의 구조가 복잡하거나 내부에 참조가 많을 경우, clone 구현이 까다롭고 오류가 발생하기 쉬움. - 예시 시스템
- 그래픽 편집기에서 도형이나 UI 컴포넌트를 복사해 붙여넣기
- 게임에서 캐릭터나 아이템 템플릿을 복제하여 개별 인스턴스로 사용
- JSON이나 XML 기반 설정 데이터를 객체로 파싱한 후, 템플릿처럼 복제해서 사용
- 실무 활용도: ⭐⭐
- 구현 난이도: ⭐⭐
23. 비지터 패턴 (Visitor)
- 개요:
비지터 패턴은 객체의 구조는 그대로 두고, 새로운 기능을 객체 외부에서 추가할 수 있도록 해주는 행동 패턴이다.
방문자 객체(Visitor)가 객체 구조 내부의 요소들을 “방문”하며 특정 연산을 수행하는 방식이다. - 원리:
각 요소(Element)는 accept(Visitor visitor) 메서드를 제공하고, Visitor는 각 요소 타입에 따른 메서드(visitConcreteElementX)를 오버로드한다.
이를 통해 객체 구조는 변경하지 않고, 방문자 객체에서 기능을 유연하게 정의할 수 있다. - 장점:
기능을 데이터 구조에서 분리할 수 있으므로, OCP(개방-폐쇄 원칙)에 맞게 기능 추가가 가능하다.
여러 종류의 연산(로깅, 렌더링, 계산 등)을 외부에서 일관된 방식으로 구현할 수 있다. - 단점:
객체 구조가 변경되면 모든 Visitor를 수정해야 하므로 객체 구조가 안정적일 때만 적합하다.
Visitor와 Element 간의 이중 디스패치(double dispatch) 개념이 필요해 설계와 이해가 어렵고 코드 복잡도가 증가할 수 있다. - 예시 시스템
- 컴파일러의 AST 분석기: 문법 노드를 방문하여 타입 검사, 최적화, 코드 생성 등의 작업 수행
- 파일 시스템 탐색기: 폴더/파일 구조를 방문하며 크기 계산, 파일 목록 출력 등
- 문서 처리 시스템: 다양한 문서 요소(텍스트, 이미지 등)를 방문하여 렌더링하거나 PDF로 내보내기
- 실무 활용도: ⭐⭐
- 구현 난이도: ⭐⭐⭐⭐
총정리 Tip:
- OCP와 SRP 원칙을 고려하면 대부분의 패턴이 왜 필요한지 이해된다.
- 패턴은 단독보다는 조합으로 사용하는 경우가 많다.
- 어떤 문제를 해결하려는지, 상황과 맥락을 항상 고려하자!
728x90
'Research > General' 카테고리의 다른 글
FastAPI 설치시 함께 설치되는 라이브러리 톺아보기 (0) | 2025.04.23 |
---|---|
[IntelliJ IDEA, Mac] Spring Boot 프로젝트 단축키 (0) | 2023.08.21 |
Comments