티스토리 뷰
Decorator (데코레이터) - Structural Pattern (구조 패턴)
Decorator 는 객체 A에 새로운 기능을 추가하고 싶을 때, 기능을 가지고 있는 특정 wrapper 객체 속에 객체 A를 배치함으로써 기능을 추가하는 디자인 패턴이다. 래퍼 안에 감싸기 때문에 Wrapper Design Pattern이라고도 불린다.
Components
Components는 래핑하는 객체랑 래핑당하는 객체 모두에게 공통의 인터페이스를 제공합니다.
Concrete Components
감싸지는 객체.
기본 동작을 가지고 있습니다. 기본 동작은 Decorator에 의해 대체됩니다.
Base Decorator
감싸지는 객체(=Concrete Components)를 참조하는 필드를 가지는 클래스입니다.
필드의 타입은 컴포넌트의 인터페이스로 선언됩니다. 이에 따라 concrete componente와 decorator를 모두 포함할 수 있습니다.
Base Decorator는 감싸지는 객체(=Concrete Components)에게 모든 기능을 위임합니다.
Concrete Decorators
Components에 추가되는 동작을 정의합니다.
Concrete Decorators는 Base Decorator의 메소드를 오버라이드합니다.
부모 메소드가 호출되기 전/후로 동작을 실행합니다.
Client
Client는 Component interface를 통해 여러 층의 Decorators를 래핑할 수 있습니다.
간단히 요약하자면 래퍼 스택에 래핑되는 객체를 쌓아 기능을 추가하는 것이 데코레이터 패턴이다.
When?
- 런타임 중에 코드를 수정하지 않고 객체에 새로운 기능을 추가할 때
- 객체에 상속을 사용하지 않고 새로운 기능을 추가할 때
Example
앱에 알림기능을 담당하는 클래스 Notifier가 있다.
이 클래스는 send() 메소드를 통해 이메일로 사용자에게 알림을 전달한다.
시간이 지나 앱이 확장되다 보니 여러가지 알람이 필요하게 되었다.
카카오톡, 페이스북, 인스타그램 등 소셜 앱들에도 알람을 전달하게 되었다.
가장 간단한 방법인 상속을 사용해 서브클래스를 만들고 send() 메소드를 사용했다.
여기까지는 괜찮아 보였다.
그런데 카카오톡을 제외하고 알람을 보내고 싶을 때도 있고, 페이스북을 제외하고 알람을 보내고 싶을 때도 있고, 인스타그램을 제외하고 알람을 보내고 싶을 때도 있고 여러가지 경우의 수가 생겼다.
상속이 너무 많이 이루어지고 코드도 복잡해졌다.
데코레이터(Decorator) 패턴을 사용할 때이다.
이제 추가 기능들은 Decorator에 의해 래핑되고 Decorator를 통해 실행된다.
래핑하는 객체와 래핑 당하는 객체 모두 같은 인터페이스를 가지고 있기 때문에 Client 입장에서는 동일하게 사용한다고 느낀다.
Code
가장 기본적인 Notifier를 Component로 구현한다.
protocol Notifier {
func send()
}
class EmailNotifier: Notifier {
func send() {
print("email 로 알람 전송")
}
}
Decorator와 추가적인 기능을 가지는 Concrete Decorator들을 정의한다.
class Decorator: Notifier {
private var component: Component
init(_ component: Component) {
self.component = component
}
/// Decorator는 component에게 send() 기능을 위임합니다.
func send() {
return component.send()
}
}
//Concrete Decorator들을 정의한다
class KakaotalkNotifier: Decorator {
override func send() {
print("카카오톡으로 알람 전송")
}
}
class InstagramNotifier: Decorator {
override func send() {
print("인스타그램으로 알람 전송")
}
}
class FacebookNotifier: Decorator {
override func send() {
print("페이스북으로 알람 전송")
}
}
Client를 선언한다.
Client는 Component 인터페이스를 사용해 객체를 사용한다. 이를 통해 Components 의 Concrete 클래스와 독립적으로 머물 수 있다.
class Client {
// ...
static func someClientCode(notifier: Notifier) {
print("Result: " + notifier.send())
}
// ...
}
Component 가 되는 emailNotifiy 인스턴스를 선언하고 Client에서 실행했다.
let emailNotify = EmailNotifier()
Client.someClientCode(notifier: emailNotify)
email로 알람이 잘 전송된 것을 확인할 수 있다.
Concrete Decorator KakaotalkNotifier와 InstagramNotifer를 사용해서 카카오톡과 인스타그램에 알람을 추가한다.
let emailNotify = EmailNotifier()
let kakaoDecorator = KakaotalkNotifier(emailNotify)
let instagramDecorator = InstagramNotifier(kakaoDecorator)
Client.someClientCode(notifier: instagramDecorator)
기존에 이메일에 더해 카카오톡과 인스타그램으로도 전송이 되는 것을 확인할 수 있다.
Result
Decorator 패턴의 장점
1. 상속없이 객체의 기능을 추가할 수 있다.
2. 런타임 중에 객체에 책임(Responsibilities)를 추가할 수 있다.
3. 객체를 여러 개의 Decorator로 래핑함으로써 여러 개의 기능을 추가할 수 있다.
4. 외부 라이브러리를 사용할 때 굳이 소스코드를 일일이 확인하고 수정할 필요가 없다.
5. 객체 지향 프로그래밍의 단일 책임 원칙(Single Responsibility Principle)를 준수한다.
단점
1. 래퍼 스택에서 특정 스택을 제거하는 것이 어렵다.
2. Decorator 스택의 순서에 따라서만 동작한다. 즉 순서를 바꾸기 어렵다.
3. 데코레이터 패턴을 너무 많이 사용하면 코드가 복잡해진다.
예제 코드를 직접 작성해보니 스택처럼 쌓인다는 것이 무슨 말인지 이해할 수 있었다.
기능을 추가하고 Client에서 메소드 한 번 실행하면 다 실행된다는 것이 편했다.
다만 스택이기에 단점에 적힌 것처럼 한 번 정한 순서를 바꾸기에는 불편했다.
'Swift > Design Pattern' 카테고리의 다른 글
[Swift Design Pattern 07] Template Method (템플릿 메소드) (0) | 2022.03.16 |
---|---|
[Swift Design Pattern 06] Adapter (어뎁터) (0) | 2022.03.15 |
[Swift Design Pattern 04] Facade (퍼사드) (0) | 2022.03.08 |
[Swift Design Pattern 03] Factory Method (0) | 2022.03.05 |
[Swift Design Pattern 02] Singleton (0) | 2022.03.04 |
- Total
- Today
- Yesterday
- 책
- 책후기
- 필독서
- 코딩
- Swift DocC
- ios
- Swift
- 부스트캠프7기
- Swift문법
- SwiftUI
- todo앱
- TODO
- 애플
- 코딩테스트
- 날씨어플
- 코딩 테스트
- Swift 서버
- 부스트캠프
- 디자인 패턴
- 책리뷰
- vapor
- 앱개발
- Combine
- Swift 디자인 패턴
- swiftUI 기초
- 부스트캠프iOS
- 개발
- UX
- 프로그래머스
- Swift공식문서
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |