[Swift 디자인 패턴] Mediator Pattern (중재자) - 디자인 패턴 공부 18
안녕하세요 Pingu입니다.🐧
지난 글에서는 행동 패턴 중 하나인 Iterator Pattern에 대해 알아봤는데요, 이번 글에서는 또 다른 행동 패턴 중 하나인 Mediator Pattern(중재자)에 대해 알아보도록 하겠습니다.
중재자 패턴이란?
Mediator Pattern은 객체 간 종속성을 줄일 수 있는 패턴입니다. 객체 간 직접 통신을 제한하고 Mediator 객체를 통해서만 통신을 가능하게 하여 상호 작용을 독립적으로 만들도록 하는 패턴입니다.
- Mediator
- Colleague 객체와 통신하기 위한 인터페이스를 정의합니다.
- 일반적으로 하나의 알람 메서드만 정의하여 Colleague와의 통신 방법을 정의합니다.
- Concrete Mediator
- Colleague 객체와 함께 작동할 행동들을 구현합니다.
- 존재하는 Colleague 객체들을 참조하고 생명주기를 관리합니다.
- Colleague Class
- 각각의 Colleague 객체들은 Mediator 객체를 참조하고 있습니다.
- 각각의 Colleague 객체들은 다른 Colleague와 통신을 할 때 마다 Mediator와 통신합니다.
- 각각의 Colleague 객체들은 다른 Colleague를 인식하지 않습니다.
중재자 패턴은 언제 쓰나요?
하나의 객체에서 발생한 이벤트에 대하여 다른 객체가 조치를 취하고 또 해당 객체가 다른 객체에게 영향을 미치는 추가적인 이벤트를 생성할 때 Mediator Pattern을 사용하면 좋습니다. 즉 객체들을 서로 독립적으로 만들기 위해 객체 간 통신을 막고 통신은 오로지 Mediator 객체를 통해서 가능하도록 만들면 됩니다.
예를 들어 유저 프로필을 만들고 편집할 수 있는 UI가 존재한다고 해볼게요. 해당 UI는 TextField, Button 등 다양한 뷰들로 구성될거에요. 그러한 뷰들 중 몇몇 뷰들은 다른 뷰들과도 상호작용을 할 수도 있겠죠? 예를 들어 나는 애완동물을 가지고 있어요 라는 체크박스를 체크하면 애완동물의 종류를 작성하는 TextField가 보인다거나 하는 방식으로 말이에요. 이때 체크박스를 체크하면 TextField가 활성화되어야 하는데, 이러한 과정을 체크박스에 이런 기능을 넣게 되면, 해당 체크박스 클래스는 다른 용도로는 사용하지 못하게 됩니다.
따라서 이러한 객체간 통신을 Mediator 객체가 수행하도록 하여 이런 문제를 해결할 수 있어요. 즉 체크박스에 체크를 했다는 이벤트를 Mediator에 전달하고 Mediator가 거기에 반응해야 하는 TextField에 이 사실을 전달하여 활성화되게 만드는 것입니다. 이렇게 하면 각각의 뷰들의 종속성이 완화되고 다양한 곳에서 재사용할 수 있게 됩니다.
중재자 패턴의 결과?
장점
- Single Responsibility Principle : 다양한 객체간 통신을 하나의 객체가 수행하도록 만들어 코드를 이해하기 쉽고 유지보수도 쉬워집니다.
- Open / Closed Principle : 객체들을 변경하지 않고도 새로운 Mediator를 도입할 수 있습니다.
- 프로그램의 다양한 구성 요소간 결합도를 줄일 수 있습니다.
- 개별 객체들을 더 쉽게 재사용 할 수 있습니다.
단점
- 시간이 지남에 따라 Mediator 객체가 God Object가 될 수 있습니다. 이 말은 상호작용을 모두 Mediator 객체가 수행하기 때문에 Mediator 객체가 복잡해지고 나중엔 유지하기 어려운 단일체가 될 수 있다는 말이 됩니다.
예제
그럼 이제 간단하게 Mediator Pattern을 Swift로 구현해보도록 하겠습니다.
아까 본 유저 프로필을 편집하는 UI 예제를 구현해보도록 할게요.
가장 먼저 Mediator 프로토콜과 Base Colleague 클래스를 하나 만들어 줍니다.
또한 이벤트를 구분하기 위한 열거형도 하나 만들어줄게요.
// Mediator
protocol Mediator {
func notify(sender: Colleague, event: EventType)
}
enum EventType {
case checkBoxSelect
case checkBoxUnselect
}
// Base Colleague
class Colleague {
var mediator: Mediator?
func setMediator(mediator: Mediator) {
self.mediator = mediator
}
}
그런 뒤 Colleague의 자식 클래스를 몇 개 만들어줍니다.
지금 필요한 것은 CheckBox, TextField 클래스일 거 같아요.
// Collegue
class CheckBox: Colleague {
var isSelect: Bool = false {
didSet {
if isSelect {
print("CheckBox 선택")
} else {
print("CheckBox 선택 해제")
}
}
}
func checkBoxClick() {
self.isSelect = !self.isSelect
if self.isSelect {
self.mediator?.notify(sender: self, event: .checkBoxSelect)
} else {
self.mediator?.notify(sender: self, event: .checkBoxUnselect)
}
}
}
// Collegue
class TextField: Colleague {
var isHidden: Bool = true {
didSet {
if isHidden {
print("TextField 비활성화")
} else {
print("TextField 활성화")
}
}
}
}
이렇게 Colleague까지 만들어주셨다면 이들의 통신을 중재할 Mediator 객체를 하나 만들어줍니다.
// Concrete Mediator
class ProfileUI: Mediator {
var checkBox: CheckBox
var textField: TextField
init(checkBox: CheckBox, textField: TextField) {
self.checkBox = checkBox
self.textField = textField
self.checkBox.setMediator(mediator: self)
self.textField.setMediator(mediator: self)
}
func notify(sender: Colleague, event: EventType) {
switch event {
case .checkBoxSelect:
self.textField.isHidden = false
case .checkBoxUnselect:
self.textField.isHidden = true
}
}
}
위와 같이 만들어주면 간단하게 Mediator Pattern 구현을 다 하게 됩니다.
Mediator에 해당하는 ProfileUI 클래스를 잘 보시면, notify 메서드, 즉 알림이 오게 되면 해당 알림의 이벤트 타입에 따라 특정 이벤트를 처리해주는 것을 볼 수 있습니다.
실제로 실행해보면 아래와 같아요.
CheckBox에는 TextField와 상호작용을 위한 아무런 코드가 없음에도 불구하고 위와 같이 checkBoxClick() 메서드를 실행하게 되면 Mediator에 의해 TextField에도 어떤 작업이 수행되는 것을 볼 수 있습니다.
이렇게 Mediator Pattern을 알아보고 간단하게 구현도 해봤습니다.
혹시라도 틀린 부분이 있다면 알려주시면 감사하겠습니다.
전체 코드는 여기에서 볼 수 있습니다.
감사합니다.