디자인 패턴중 구조패턴인 데코레이터 패턴을 활용한 에러처리 방법입니다.
데코레이터 패턴
데코레이터 패턴은 디자인패턴중 구조패턴에 속하는 패턴입니다.
Wrapper 패턴이라고도 불립니다.
데코레이터 패턴은 새로운 행동을 정의한 Wrapper내부에 Wrapper를 넣어서 행동들을 해당 겍체들이 담당하도록 하는 구조 패턴입니다.
마트료시카라는 러시아 인형을 예시로 들면 적합할것 같습니다.
이 인형 안에 인형이, 그 인형 안에 또 인형이 들어있는 구조입니다
이 인형 하나가 Wrapper가 되고 그 Wrapper가 또 Wrapper를 가지고 있는 구조입니다. 각자의 Wrapper는 각자의 역할을 담당합니다.
데코레이터 패턴에 대해서는 좋은 설명이 많은 글이 많으니 깊게 설명하지는 않겠습니다.
데코레이터 패턴의 특징은 두 가지입니다.
1. "동적"으로 런타임에 기능을 변경하고 추가할 수 있습니다.
2. 기존 객체를 건들지 않는 방식으로 기능을 추가할 수 있습니다.
1. 핵심 기능을 담당하는 Component Interface(Protocol)
public protocol ErrorHandler {
func handleError(error: Error)
}
2. 이를 채택해서 구현하는 ConcreteComponent 타입을 구현합니다.
class ConcreteErrorHandler: ErrorHandler {
func handleError(error: Error) {
print(error)
}
}
여기서 Error를 프린트하는 Concrete 컴포넌트를 작성했습니다.
근데 만약 에러 발생시 UI변경이 필요한 상황이 생기면 어떻게 할까요
UI업데이트를할 데코레이터를 추가해주면 됩니다.
3. 데코레이터 구현
class UpdateUIErrorHandlerDecorator: ErrorHandler {
private var wrapped: ErrorHandler
private var updateUI: (() -> Void)
init(
wrapped: ErrorHandler,
updateUI: @escaping (() -> ())
) {
self.wrapped = wrapped
self.updateUI = updateUI
}
func handleError(error: Error) {
wrapped.handleError(error: error)
DispatchQueue.main.async { [weak self] in
self?.updateUI()
}
}
}
이제 Concrete를 꾸며줄 데코레이터들을 추가하면 됩니다.
이 데코레이터들은 Wrapper들을 소유합니다.
데코레이터를 보면 wrapped라는 Component Interface인 ErrorHandler라는 타입을 소유합니다.
생성자에서는 updateUI라는 클로저를 받는데 이 데코레이터를 사용하는 뷰 혹은 뷰모델에서 업데이트할 UI를 정의해주면 됩니다.
만약 뷰 모델에서 통신에 실패하고 서버에서 받은 에러를 뷰에 보여줘야 할 경우 이를 어떻게 사용할 수 있을까요
에러가 발생했을 때 광고를 숨겨야 한다고 가정해봅시다.
open class ViewModel: ObservableObject {
@Published var isAdShowing: Bool = true
@Published var isShowingError: Bool = false
@Published var errorMessage: String = ""
private var errorHandler: ErrorHandler?
init() {
let concreteErrorHandler: ErrorHandler = ConcreateErrorHandler()
let updateUIDecorator: ErrorHandler = UpdateUIErrorHandlerDecorator(
wrapped: concreteErrorHandler,
updateUI: { [weak self] in
self?.isAdShowing = false
}
)
self.errorHandler = updateUIDecorator
}
}
근데 만약 런타임에 에러 처리를 바꿔야 한다면? UI업데이트가 사라져야 한다면?
그 시점에 errorHandler를 변경해주면 됩니다.
// 어떤 시점에
let concreteErrorHandler: ErrorHandler = ConcreateErrorHandler()
self.errorHandler = concreteErrorHandler
어떤 타입의 에러를 처리하는 데코레이터, 에러 발생 로그를 서버로 보내는 데코레이터 등 필요한 기능을 조합할 수도, 런타임에 변경할 수 있으니 더욱 유연한 에러처리 방법이 될 수 있습니다.
에러 처리 로직을 하나의 객체가 담당하게 하므로 불필요한 보일러플레이트 코드를 줄일 수 있게 되겠죠.
Solid 원칙중 하나인 단일 책임 원칙 SRP(Single Responsibility Principle)을 준수하게 됩니다.
혹시 틀린점이나 부족한 점이 있으면 알려주시면 감사하겠습니다.