| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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
- PROJECT
- 비트필드를 이용한 dp
- R 그래프
- map
- 최소 공통 조상
- DP
- Delete
- Binary Lifting
- trie
- JavaScript
- 2-SAT
- 강한 연결 요소
- Strongly Connected Component
- 이분 탐색
- Github
- Express.js
- Spin Lock
- 벨만-포드
- localstorage
- Behavior Design Pattern
- 그래프 탐색
- Lock-free Stack
- SCC
- 게임 서버 아키텍처
- reference counting
- 트라이
- 자바스크립트
- 비트마스킹
- ccw 알고리즘
- Prisma
Archives
- Today
- Total
dh_0e
[Design Pattern] Structural Design Patterns II (Composite Pattern, Decorator Pattern) 본문
Software Analysis & Design/Design Patterns
[Design Pattern] Structural Design Patterns II (Composite Pattern, Decorator Pattern)
dh_0e 2025. 11. 11. 15:46
Composite Pattern(복합체 패턴)
- 전체-부분 관계의 트리 구조로 표현되는 객체들을 단일 객체(Interface)처럼 취급할 수 있게 해주는 구조 패턴
- 단일 객체(Leaf)와 복합 객체(Composite)를 동일한 인터페이스를 사용하여 처리
- Leaf는 하위 요소가 없는 단일 객체를 의미하며, Composite는 Leaf나 다른 Composite 객체들을 포함하는 복합 객체

Composite Pattern 필요 상황 예시

- 주문 시스템에서 여러 상품을 담은 상자의 가격을 계산하려고 할 때
- 한 box에 여러 product와 작은 box들을 담을수 있음
- 작은 box들도 여러 product를 담을 수 있음
- Origin: 상자의 가격을 계산하기 위해서는 내부 제품을 모두 살펴보면서 가격을 합산해야 함
- 트리 전체 순회를 하면서 box와 product의 타입을 구분해주는 코드를 구성해야 하므로 코드가 복잡해질 수 있음
- Composite Pattern Idea
- 총가격을 계산하는 메서드($getPrice()$)를 가지는 공통 인터페이스(Component)를 통해서 products와 boxes에 대해 동일하게 작업하게 할 수 있음
- Product의 경우 단순히 제품의 가격을 반환
- Box의 경우 Box내 products의 총 가격을 반환
- 트리를 구성하는 객체들의 구체적인 클래스 타입에 대해 신경 쓸 필요 없음(현재 보고있는 객체가 product인지 box인지 알 필요가 없음)
- 복합체 패턴은 그릇과 내용물을 동일 시 해서 재귀적인 구조를 편하게 다룰 수 있게 해줌
- 총가격을 계산하는 메서드($getPrice()$)를 가지는 공통 인터페이스(Component)를 통해서 products와 boxes에 대해 동일하게 작업하게 할 수 있음

- 최상위: Component(요소)
- 인터페이스(or 추상 클래스)
- 공통 연산 execute()를 정의
- 하위 클래스 1: Leaf
- Component를 상속/구현
- 오버라이딩된 execute() 안에서 실제 일을 직접 수행
- 더 이상 자식이 없는 "말단 노드"
- 하위 클래스 2: Composite(복합체)
- Component를 상속/구현
- 자기 안에 children: Component[]를 가지고 있음 >> 포함 관계(합성)
- Composite는 Component를 상속받으면서, 자기 안에 여러 Component들을 포함(합성)하는 구조
- Composite에 포함되는 Component는 Composite가 사라지면 함께 사라짐(합성 관계)
- execute()를 오버라이딩해서 children에 들어있는 각 Component의 execute()를 호출하도록 위임
- 자식 관리용 메서드(add, remove, getChildren)를 가짐
- Client
- 어떤 구체 클래스도 상속하지 않음
- 단지 Component 타입에 의존해서 사용함
- Leaf인지 Composite인지 신경 안 쓰고, 전부 Component로 다형성 있게 다룸



사용 사례) Java Swing

- 사용 시기
- 객체의 구조가 트리로 표현되는 상황에서, 단일/복합 객체의 관계를 단순화하여 균일하게 처리하고 싶을 때
- 장점
- 다형성 재귀를 통해 복잡한 트리 구조를 보다 편리하게 다룰 수 있음
- 새로운 leaf 클래스 추가하더라도 클라이언트에는 영향 없음 (OCP:개방 폐쇄 원칙 준수)
- 단점
- 재귀 호출 특징 상 트리 깊이가 깊어지면 디버깅에 어려움이 생김
- 기능이 너무 다른 클래스들 간에는 공통 인터페이스 설계가 까다로움
- Component에 선언되는 메소드가 공통으로 활용될 수 있는 의미를 가져야 함
Decorator Pattern(데코레이터 패턴)
- 객체에 추가적인 기능을 동적으로 더할 수 있게 해주는 구조 패턴
- 컴파일 타임이 아닌 런타임에 객체에 대한 기능 확장이 가능
- 데코레이터(장식자)라는 뜻은 기본 제품에 재료/디자인을 추가 및 변경 해줌으로써 새로운 종류의 제품을 만들어내는 것을 의미
- 필요한 상황
- ex) 사용자에게 이벤트 알람을 주기 위한 알람 라이브러리 설계
- Afflication에서는 Notifier를 통해 기본적으로 이메일로 알람을 함
- 어느 시점부터 사용자들이 이메일 이상의 알람 기능을 원하여 아래와 같이 상속 구조로 Notifier를 확장했다고 가정
- 상속 구조에서는 한번에 특정 Notifier만 동작하게 됨

- 여러 채널을 통해서 알람을 보내고 싶다면?
- 상속 구조를 유지한 채로는 모든 조합에 따른 Notifier를 구현해야 함
- 코드의 양도 늘어나고 확장의 유연성 떨어짐
- 상속 구조를 유지한 채로는 모든 조합에 따른 Notifier를 구현해야 함

- 이미 있는 여러 Notifier들을 runtime에 조립할 수 있을까? >> 데코레이션 패턴

- Component
- 인터페이스 or 추상 클래스
- 공통 메소드: execute()
- 기본 기능을 하는 애든, 데코레이터든 전부 component 타입으로 취급하겠다는 역할
- Concrete component
- Component를 상속/구현한 실체 클래스
- 원래 기본 기능을 진짜로 수행하는 곳
- 아직 아무런 추가 기능(Deco)이 없는 순수한 객체
- Base Decorator
- Component를 상속/구현
- 내부에 wrappee: Component 필드를 하나 더 가지고 있음
- 이 wrappee 필드와 포함/집합 관계를 가짐
- Base Decorator에 포함되는 wrappee는 Base Decorator가 사라져도 유지됨 (집합 관계)
- 생성자로 c: Component 어떤 Component든 받아서 wrappee에 저장
- "나는 껍데기고, 진짜 일은 나를 감싸고 있는 애(wrappee)한테 시킨다"라는 느낌
- Concrete Decorators
- ConcreteDecorator1, ConcreteDecorator2, .. 같은 애들
- BaseDecorator를 상속
- execute()를 오버라이드해서
- 필요하면 추가 작업 진행 후 super.execute() or wrappee.execute()를 호출해서 원래 행동을 수행하게 함
- super.execute(): 자식 execute() >> 부모 execute() >> wrappee.execute()
- wrappee.execute(): 자식 execute() >> wrappee.execute()
- 또는 super 호출 전/후에 둘 다 끼워넣기도 함
- 필요하면 추가 작업 진행 후 super.execute() or wrappee.execute()를 호출해서 원래 행동을 수행하게 함
- Client
- 오직 Component 타입에만 의존
- 사용하는 흐름은 위 예시처럼
- a = new ConcComponent(): 기본 컴포넌트
- b = new ConcDecorator1(a): a를 감싼 데코레이터1
- c = new ConcDecorator2(b): b를 또 감싼 데코레이터2
- c.execute() 호출
- 데코레이터2의 execute()
- 데코레이터1의 execute()
- 마지막에 ConcreteComponent의 execute()가 순차적으로 호출됨
- 정리
- 상속
- Component << ConcreteComponent
- Component << BaseDecorator << ConcreteDecorator
- 포함(합성)
- BaseDecorator 안에 wrappee: Component가 포함됨
- wrappee에는 ConcreteComponent도 들어갈 수 있고, 또 다른 ConcreteDecorator가 들어가서 계속 감쌀 수도 있음
- BaseDecorator 안에 wrappee: Component가 포함됨
- 상속
- Decorator는 Component를 상속받으면서, 자기 안에 다른 Component를 필드로 포함해서, 기존 객체를 감싸고 런타임 도중 그 앞뒤에 기능을 덧붙이는 패턴




- 위 코드를 실행하면 E-mail, Facebook, SMS, Slack 순서대로 출력됨
- 호출 순서: Slack에서 super 호출 >> wrappee에 있는 SMS.send 호출 >> SMS에선 또 super.send 호출
>> Facebook.send에서 super.send로 DefaultNotifier.send 호출 - return 순서: DefaultNotifer.send에서 email send하고 return >> Facebook send하고 return
>> SMS send하고 return >> Slack send 하고 return(끝)
- 호출 순서: Slack에서 super 호출 >> wrappee에 있는 SMS.send 호출 >> SMS에선 또 super.send 호출
Java에서 Decorator Pattern 사용 사례
- Stream
- 어댑터이면서 동시에 Decorator라고 볼 수 있음

- Collections
- checkedList(), synchronizedList(), unmodifiableList() 등등

사용 시기
- 객체 책임과 행동이 동적으로 상황/조건에 따라 다양한 기능이 빈번하게 추가/삭제되는 경우
- 객체를 생성하는 코드에 변경이 없으면서 런타임에 추가 가능
- 객체의 결합을 통해 기능이 생성되어야 하는 경우
- 상속을 통해 서브 클래싱으로 객체의 동작을 확장하는 것이 어색하거나 불가능할 때
Decorator Pattern 장단점
- 장점
- 상속을 통해 서브클래스로 확장하는 것보다 훨씬 유연하게 기능을 확장할 수 있음
- 객체를 여러 데코레이터로 래핑하여 여러 동작을 클라이언트가 자유롭게 결합할 수 있음
- 단일 책임 원칙(SRP) 준수: 각 데코레이터 클래스마다 고유의 책임을 가짐
- 개방 폐쇄 원칙(OCP) 준수: 클라이언트 코드 수정 없이 데코레이터 클래스 추가만으로도 기능 확장이 가능함
- 의존 역전 원칙(DIP) 준수: 구현체가 아닌 인터페이스를 바라보기 때문에
- Application(Client) 입장에서 Notifier 타입만 보고 있으며 Decorator가 얼마나 감싸져 있는지 모름
상위 모듈(Application)이 추상(Notifier)에만 의존하고,
구체 구현(DefaultNotifier/Decorators)은 그 추상에 맞춰짐
- Application(Client) 입장에서 Notifier 타입만 보고 있으며 Decorator가 얼마나 감싸져 있는지 모름
- 단점
- 만일 Decorator 일부를 동적으로 제거하고 싶다면, wrapper 스택에서 특정 wrapper를 제거하는 것이 어려움
- 데코레이터를 조합하는 방식에 코드 복잡도가 올라갈 수 있음
- 어느 장식자를 먼저 데코레이팅 하느냐에 따라 데코레이터 스택 순서가 결정지어지게 되므로, 순서에 의존하지 않는 방식으로 데코레이터를 구현하기 어려움
- 어느 데코레이터를 먼저 감싸느냐에 따라 그에 대한 행동이 따르기에 순서에 유의
Summary
- 복합체 패턴(Composite Pattern)
- 전체-부분 관계의 트리 구조로 표현되는 객체들을 단일 객체처럼 취급할 수 있게 해주는 구조 패턴
- 데코레이터 패턴(Decorator Pattern)
- 객체에 추가적인 기능을 동적으로 더할 수 있게 해주는 구조 패턴
- 컴파일 타임이 아닌 런타임에 객체에 대한 기능 확장이 가능
