#### tag: `디자인 패턴`
비밀번호 : 1372
# 디자인 패턴
## Proxy
### 문제
방대한 양의 시스템 자원을 소비하는 거대한 객체가 있다고 가정해봅시다.
이 객체는 필요할 때가 있지만, 항상 필요한 것은 아닐 겁니다.
저희는 이를 위해서 실제로 필요한 경우에만 객체를 만들어서 지연된 초기화를 구현할 수 있습니다. 그러면, 객체의 모든 클라이언트들은 어떤 지연된 초기화 코드를 실행해야 하는데,
이는 많은 보일러 플레이트 코드를 양성할 것입니다.
이 코드를 객체의 클래스에 직접 넣을 수 있을 수 있습니다.
하지만, 이것이 무조건 가능한 부분이 아닙니다.
가령 예를 들어서 타사 라이브러리의 일부에 주입하는 것이 불가능할 수도 있습니다.
### 아이디어
#### 아이디어 1
이 패턴의 아이디어는 자원에 직접적으로 접근을 관리할 수 있고, 이미 접근 했던 데이터를 기억하는 역할들을 하게 됩니다. 즉, 프록시라는 객체를 통해서 자원에 접근하도록 제한하고, 이를 통해서 캐싱을 할 수 있다는 것입니다.
#### 아이디어 2
예를 들어서, 신용카드는 은행 계좌의 프록시이며, 은행 계좌는 현금의 프록시입니다. 이 두개의 프록시는 모두 같은 인터페이스를 통해서 구현이 되며, 모두 결제를 할 수 있는 능력을 가지게 됩니다. 이와 같은 아이디어를 통해서 구현하게 됩니다.
### 패턴 정의
#### 패턴 정의 개념
프록시는 다른 객체에 대한 대체 또는 자리 표시자를 제공할 수 있는 구조적 디자인 패턴입니다.
프록시는 원래 객체에 대한 접근을 제어하므로,
요청이 원래 객체에 전달되기 전 또는 후에 무언가를 수행할 수 있도록 합니다.
#### 프록시 3가지 역할
프록시는 크게 3가지로 구분된다.
- Remote : 원격 프록시 라고 하며, 요청을 처리하고 서비스 객체에 이를 전달하는 역할을 담당한다. 즉 원격 객체에 대한 대변자 역할하는 객체이다.
- Virtual : 서비스 객체에 대한 정보를 캐싱하여 접근을 연기합니다. 즉 반드시 필요로 하는 시점까지 객체의 생성을 연기하고 필요 할 때만 생성한다.
- Protection : 특정 작업을 요청한 객체가 해당 작업을 수행할 권한을 가지고 있는지 확인한다. 즉 RealSubject 에 대한 접근을 제어하기 위한 경우에 객체에 대한 접근 권한을 제어하거나, 객체마다 접근 권한을 달리하고 싶을 때 상용한다
### 예시
class MainVideoDownloadService()
class SmartVideoDownloadService()
비디오를 다운받는 예시를 들어보겠슴
protocol VideoDownlodaService()
프로토콜은 인증을위한 auth, 비디오를 다운받을 getVideo 메서드가 있다고 합시다.
원래라면 class MainVideoDownloadService 하나만으로 영상이 필요 할 때마다 직접 다운로드 해도됨
하지만,
Cache가 있다고 생각해보면 캐시가 존재 할 때, 다운받지 않아서 다운받는 부담을 줄일 수 있을것 임
그래서 프록시를 사용하면
SmartVideoDownloadService라는 프록시 패턴적용을 위한 클래스를 만들어
다음과 같이 할 수 있따릿
#### 장점
- 클라이언트들이 알지 못하는 상태에서 서비스 객체를 제어할 수 있다.
- 클라이언트들이 신경 쓰지 않을 때 서비스 객체의 수명 주기를 관리할 수 있습니다.
- 프록시는 서비스 객체가 준비되지 않았거나 사용할 수 없는 경우에도 작동합니다.
- 개방/폐쇄 원칙. 서비스나 클라이언트들을 변경하지 않고도 새 프록시들을 도입할 수 있습니다.
#### 단점
- 새로운 클래스들을 많이 도입해야 하므로 코드가 복잡해질 수 있습니다.
- 서비스의 응답이 늦어질 수 있습니다.
---
## Template Method
### 문제
저희가 동일한 절차를 가지고 어떤 결과물을 만들어내야 하는 값이 있다고 가정 해볼 것입니다. 예를 들어서, 커피를 만드는 과정을 통해서 이야기를 해볼게요.
다양한 커피를 제작하는 일련의 과정을 큰 그림들로 묶어보면, `받은 주문을 확인한다` , `에스프레소 샷을 뽑는다.` , `해당 커피 레시피에 맞게 첨가물을 넣는다` 로 생각할 수 있습니다.
그렇지만, 어떤 커피는 에스프레소가 2샷이 들어갈 것이고, 다른 커피는 3샷이 들어갈 것입니다. 또한, 해당 커피 레시피에 의해서 첨가물들이 변경되게 될 것입니다.
이와 같은 상황에서 저희는 각각의 커피에 대한 클래스를 구성하는 것이 적절할 것입니다. 그렇지만, 저희는 모두가 커피를 만들고 싶은 목적이 있기 때문에 한개의 목적에 대한 함수를 통해서 내부적으로 값들을 변경 시키면 될 것입니다.
즉, 이 템플릿 패턴을 통해서 해결하고 싶은 문제는 다양한 형식이 필요하고, 이를 통해서 동일한 데이터의 형식을 추출하는 문제에 대해서 해결하고자 하는 것으로 볼 수 있습니다.
,,,
### 아이디어
이 패턴이 활용하는 아이디어는 추상 클래스를 활용하는 것입니다.
추상 클래스를 통해서 이 클래스로 구성되는 자식 클래스가 내부적으로 구현을 할 수 있도록 하는 아이디어를 사용합니다.
이를 Swift 언어에 대입을 하게 되면, Protocol과 Class를 활용하는 것을 볼 수 있을 겁니다.
### 패턴 정의
즉, 템플릿 메서드는 부모 클래스에서 어떤 데이터를 생성하는 절차의 골격을 정의하지만, 해당 알고리즘의 구조를 변경하지 않고 자식 클래스의 알고리즘의 특정 단계들을 변경하도록 하는 패턴입니다.
### 예시
---
## Visitor (방문자)
### 문제
20. 잘 짜여진 타입에 대해서 새로운 기능을 추가하기 위해서 우리는 어떤 방법을 활용할까요? 대부분 생각하는 방법은 POP로 구성되어 있다면, Protocol에 함수를 추가하고, 이에 대한 함수를 기본 구현을 하는 방법을 생각할 수 있습니다. 아니면, 상속이 되어 있다면, 부모 클래스에 함수를 추가한다던가 하는 방법등을 생각할 수 있습니다.
21. 하지만 이와 같은 해결 방법을 활용하게 되면, 어떤 타입에서는 필요하지 않은 메서드가 추가될 수 있습니다. 또한, 기능을 새롭게 추가하면서 생기는 변경사항들이 예기치 못한 버그를 발생시킬 우려가 있습니다.
예를들어 이런 문제점이 있는지 모르는 상황에서 위와 같은 방법들을 통해서 모든 구현을 끝냈다고 한다면
다음날 기획자 분이 와서 `저희 새로운 기능이 추가 되어야 할 것 같습니다.` 라고 이야기를 할 수도 있습니다.
22. 그렇다면, 저희는 또 새로운 함수를 구현하고, 이와 맞는 타입에 대한 조건문을 만드는 작업을 수행해야 할 것입니다.
아니면, 새로운 함수를 구현하였지만, 어떤 타입에서는 필요하지 않아서 새로운 다형성을 구성해야 하는 일도 생길 것입니다.
이처럼 새로운 기능을 추가할 때 마다 코드가 복잡해지는 문제를 해결하기 위해서 visitor 패턴을 활용하게 됩니다.
### 아이디어
23. 이 패턴의 아이디어는 새로운 기능을 기존의 타입이 아닌 다른 별도의 클래스를 만들어 그 곳에 배치하는 것입니다.
또한 visitor 메서드의 인자로 타입이 전달되어 기능에 필요한 모든 데이터에 접근할 수 있도록 합니다.
만약 타입이 서로 다른 클래스로 만들어져 있다면
각각의 타입마다 필요한 실제 구현은 조금씩 다르게 될 것입니다.
그래서 각 메서드가 다른 유형의 인수를 받을 수 있도록 구성하는 것입니다.
새로운 기능을 기존 타입에 추가하는 대신 visitor라는 별도의 클래스에 배치하면서
visitor의 메서드에 타입을 인자로 전달시킵니다.
이렇게 하면 메서드는 기능에 필요한 모든 데이터에 접근할 수 있게됩니다.
하지만 이런 해결 방법을 통해서 완전한 해결을 할 수는 없습니다.
24. 저희는 다시 각 타입에 대한 조건을 구현하게 될 것입니다. 이런 타입에 대한 조건을 지속적으로 구현하게 된다면, 100개의 타입에는 100개의 조건문이 생성되게 될 것입니다.
25. 이를 해결하기 위해서 더블 디스패치라는 방법을 사용할 수 있습니다.
먼저, visitor를 accept할 수 있는 추상화 타입을 만들고 이를 채택합니다.
26. 호출할 적절한 메서드를 선택하는 대신 이 선택권을 Visitor 객체에게 위임하게 됩니다.
이로써 제일 위의 for문 처럼 visitor를 인자로 받기만 하면 각 타입에 맞는 메서드가 실행되게 됩니다.
### 패턴정의
27. visitor패턴은 새로운 기능이 추가될 때, 최대한 기존의 코드를 변경하지 않으면서 기능을 추가할 수 있도록 설계가 되어야 한다는 원칙인 개방폐쇄 원칙을 지키고 타입의 관심사에 맞게 로직을 분리해내는 행위적 패턴입니다.
### 예제
28. 테이블 뷰의 셀의 타입에 따라 다른 기능을 하는 예시를 알아보겠습니다.
먼저 비지터를 사용하지 않는 경우, 아래 처럼 각 타입에 새로운 기능을 추가하거나, 프로토콜을 채택해서 공통기능을 추가할 수 있습니다.
29. 테이블 뷰의 셀을 클릭했을 때, 셀의 타입을 다운캐스팅을 통해 확인해서 원하는 로직을 실행합니다.
만약 셀이 30개라면 if분기는 많아질 것이고 코드가 복잡해질 것입니다.
30. visitor가 방문할 수 있도록 VisitableCell 이라는 프로토콜을 만들어주어 채택해서 accept를 구현해줍니다.
이 때 visitor에 타입을 인자로 넣게되어 메서드 기능에 필요한 모든 데이터에 접근 가능하도록 해줍니다.
31. 다음은 Visitor를 구현하는데, 각 타입에 대한 기능에 맞게 로직을 구현해줍니다.
32. 이렇게 visitor를 받아들일 수 있는 타입으로 다운캐스팅 해서 accept만 해준다면 각 타입에 맞게 로직이 실행됩니다. 또 이전 코드와 달리 좀 더 깔끔해진 모습을 볼 수 있습니다.
여기까지 예시를 알아보았습니다.
### 장단점
33. visitor패턴의 장점으로는 기존 타입의 코드의 수정을 최소화 하면서 기능을 쉽게 추가할 수 있다는 장점과 타입과 타입이 해야하는 일인 로직을 따로 분리해낼 수 있다는 장점이 있습니다.
34. 단점으로는 새로운 타입이 추가될 때마다 visitor에 타입을 인자로 받는 메서드가 추가되어 로직이 추가되어야 합니다. 또한 타입과 visitor의 결합도가 높아지는 단점이 있습니다.
끝
## 테이블 뷰 셀 별로 설정 해주기
https://lazarevzubov.medium.com/visitor-design-pattern-in-ios-and-swift-universe-e7a953341a6f
```swift
class TableVC: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(FirstCell.self,
forCellReuseIdentifier: "FirstCell")
tableView.register(SecondCell.self,
forCellReuseIdentifier: "SecondCell")
tableView.register(ThirdCell.self,
forCellReuseIdentifier: "ThirdCell")
}
override func tableView(
_ tableView: UITableView, cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
if indexPath.row % 3 == 0 {
return FirstCell()
} else if indexPath.row % 3 == 1 {
return SecondCell()
} else if indexPath.row % 3 == 2 {
return ThridCell()
}
}
}
struct BackGroundColorVisitor {
func visit(_ сell: FirstCell) {
cell.contentView.backgroundColor = .blue
}
func visit(_ сell: SecondCell) {
cell.contentView.backgroundColor = .green
}
func visit(_ сell: ThirdCell) {
cell.contentView.backgroundColor = .yellow
}
}
protocol BackGroundColorVisitable {
func accept(_ visitor: BackGroundColorVisitor)
}
extension FirstCell: BackGroundColorVisitable {
func accept(_ visitor: BackGroundColorVisitor) {
visitor.visit(self)
}
}
extension SecondCell: BackGroundColorVisitable {
func accept(_ visitor: BackGroundColorVisitor) {
visitor.visit(self)
}
}
extension ThirdCell: BackGroundColorVisitable {
func accept(_ visitor: BackGroundColorVisitor) {
visitor.visit(self)
}
}
```
```swift
class TableVC: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(FirstCell.self,
forCellReuseIdentifier: "FirstCell")
tableView.register(SecondCell.self,
forCellReuseIdentifier: "SecondCell")
tableView.register(ThirdCell.self,
forCellReuseIdentifier: "ThirdCell")
}
override func tableView(
_ tableView: UITableView, cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
(cell as! BackGroundColorVisitable).accept(BackGroundColorVisitor())
}
}
}
```