# 디자인패턴 - 3조
[🍪써니쿠키: `디자인패턴 뛰어들기` 책을 참고했습니다]
# 1️⃣ 책임 연쇄 패턴 (Chain of Responsibility)
책임 연쇄 패턴은 핸들러들의 체인(사슬)을 따라 요청을 전달할 수 있게 해주는 행동 디자인 패턴입니다.
각 핸들러는 요청을 받으면 요청을 처리할지 아니면 체인의 다음 핸들러로 전달할지를 결정합니다.
## 🔺 문제 상황
온라인 주문 시스템을 개발하고있다고 가정해봅시다.
인증된 사용자들만 주문 할 수 있게 제한할거고, 인증 후 VIP 사용자에게는 특정 페이지의 접근 권한을 부여하려고 합니다.
즉, 시스템에 자격 검증을 요청하면 인증확인 후 ➡️ 권한을 부여하는 수행을 **순차적으로** 진행해야합니다. 인증단계에서 실패하면 다른 검사를 진행할 필요가 없습니다.
<img width = 600, src ="https://i.imgur.com/rk8Fadt.png">
그 후,
시스템을 운영하면서 몇 달간 이런 순차 검사 몇가지가 더 추가됐습니다.
시스템이 무차별 대입공격에 취약하다는 사실이 발견돼서 이런 공격을 방어하기 위해 같은 IP주소에서 반복적으로 시도하는 실패요청을 걸러내는 검사를 추가했습니다.
또, 동료가 시스템 속도를 높이기 위해서 같은 데이터가 포함된 요청에 대해서는 캐싱을 사용하자는 의견에 데이터 캐싱여부를 확인하는 검사를 추가했습니다.
<img width = 600, src ="https://i.imgur.com/S20NESQ.png">
코드가 커질수록 차례대로 진행해야하는 작업들이 복잡해지고 엉망진창이 되었습니다
하나의 검사코드를 바꾸면 다른 검사코드가 영향을 받기도 하는 상황입니다.
검사의 일부를 재사용하려고 해도 연쇄적인 모든검사과정이 따라오니 재사용하지 못해 코드를 복사해서 써야하는 상황입니다.
## 🔫 해결책
이럴 때! 이런 문제를 **책임 연쇄 패턴** 으로 해결할 수 있습니다.
책임 연쇄 패턴에서는 특정 행동들을 **핸들러**라는 독립 실행형 객체들로 변환합니다.
위의 예제라면 각 검사가 검사를 수행하는 단일 메서드가 있는 Class로 추출되어야합니다.
그 후, 핸들러들을 체인으로 연결합니다. 연결된 각 핸들러에는 체인의 다음 핸들러에 대한 참조를 저장하기 위한 프로퍼티가 있습니다. 이제 요청은 처리가 되는 핸들러를 만날 때까지 체인을 따라 이동합니다. 각 핸들러들은 더 이상 다음 체인으로 전달하지 않고 중지할지 다음으로 보낼지 결정을 내릴 수 있습니다.
## 🔎 구조 (다른언어라 Swift에 맞게 그림 바꿔야함 코드는 맨아래..첨부)
<img width = 400, src ="https://i.imgur.com/iVskwJ4.png">
젤 하단에 코드 예시있는데.. 그 코드를 바탕으로 작성햇습니다(더 쉬운 코드를 찾아보게쑴...)
1. Handler 프로토콜이 공통적인 인터페이스를 선언합니다.
- 체인을 만들 메서드(setNext)를 선언합니다
- 요청을 처리하는 메서드(handle)를 선언합니다
- 다음 체인핸들러를 참조할 nextHadler프로퍼티를 선언합니다.
```swift
protocol Handler: AnyObject {
var nextHandler: Handler? { get set }
func setNext(handler: Handler) -> Handler
func handle(requests: [String], index: Int)
}
```
2. (선택사항) 프로토콜 Extension으로 공통 기본구현
- 체인을 만드는 메서드(setNext)에서 nextHadler를 지정해주는 기본구현을 해줍니다.
- 요청을 처리하는 메서드(handle)에서 다음핸들러의 존재여부를 확인하고 다음 핸들러로 실행을 넘깁니다.
```swift
extension Handler {
func setNext(handler: Handler) -> Handler {
self.nextHandler = handler
return self
}
}
```
3. 모든 구체화 Class들은 Handler 프로토콜을 채택하고, 요청을 처리하는 실제 코드를 구현합니다.
```swift
class 반복접근확인Handler: Handler {
var nextHandler: Handler?
func handle(requests: [String], index: Int) {
if (requests[index] == "5번 이하의 반복시도") {
print("정상접근입니다 ✅")
nextHandler?.handle(requests: requests, index: index + 1)
} else {
print("반복접근입니다❌")
return
}
}
}
class 인증Handler: Handler {
var nextHandler: Handler?
func handle(requests: [String], index: Int) {
if (requests[index] == "유효한 인증") {
print("유효한 인증입니다 ✅")
nextHandler?.handle(requests: requests, index: index + 1)
} else {
print("유효한 인증이 아닙니다 ❌")
return
}
}
}
class VIPHandler: Handler {
var nextHandler: Handler?
func handle(requests: [String], index: Int) {
if (requests[index] == "VIP") {
print("VIP용 접근 권한설정 ✅")
} else {
print("VIP 아님 ❌")
return
}
}
}
```
4. Client에서는 원하는 요청을 매개변수에 담아 핸들러로 넘깁니다. 이 때, 원하는 체인의 모든 핸들러로 보낼 수 있으며 꼭 첫번째 핸들러일 필요는 없습니다.
```swift
class Client {
static func checkIdentifier(requests: [String], handler: Handler) {
print("-------------------------")
print("자격검증을 시작합니다")
handler.handle(requests: requests, index: 0)
}
}
```
5. 확인
```swift
let 반복된실패요청확인 = 반복된실패요청Handler()
let 인증확인 = 인증Handler()
let vip확인 = VIPHandler()
let 자격검증1 = ["5번 이하의 반복시도❌", "유효한 인증 ❌", "VIP ❌"]
let 자격검증2 = ["5번 이하의 반복시도", "유효한 인증 ❌", "VIP ❌"]
let 자격검증3 = ["5번 이하의 반복시도", "유효한 인증", "VIP ❌"]
let 자격검증4 = ["5번 이하의 반복시도", "유효한 인증", "VIP"]
//핸들러 체이닝
let handler = 반복된실패요청확인.setNext(handler: 인증확인)
인증확인.setNext(handler: vip확인)
Client.checkIdentifier(requests: 자격검증1, handler: handler)
Client.checkIdentifier(requests: 자격검증2, handler: handler)
Client.checkIdentifier(requests: 자격검증3, handler: handler)
Client.checkIdentifier(requests: 자격검증4, handler: handler)
/*
-------------------------
자격검증을 시작합니다
반복접근입니다❌
-------------------------
자격검증을 시작합니다
정상접근입니다 ✅
유효한 인증이 아닙니다 ❌
-------------------------
자격검증을 시작합니다
정상접근입니다 ✅
유효한 인증입니다 ✅
VIP 아님 ❌
-------------------------
자격검증을 시작합니다
정상접근입니다 ✅
유효한 인증입니다 ✅
VIP용 접근 권한설정 ✅
*/
```
## ✅ 결론
- **특정 순서로 여러핸들러를 실행해야 할 때 사용합니다.**
- 체인의 핸들러들을 원하는 순서로 연결할 수 있으므로 모든 요청은 정확히 계획한 대로 체인을 통과합니다.
- **장점**
- 요청의 처리 순서를 제어할 수 있습니다.
- 단일 책임 원칙을 지킬 수 있습니다. 작업을 호출하는 클래스들을 작업을 수행하는 클래스들과 분리할 수 있습니다.
- 개방/폐쇄 원칙을 지킬 수 있습니다. 기존 클라이언트 코드를 손상하지 않고 앱에 새 핸들러들을 도입할 수 있습니다.
- **단점**
- 일부 요청들은 처리되지 않을 수 있습니다.
---
[🍪써니쿠키: 정리된 블로그 글들과 `디자인패턴 뛰어들기` 책을 참고했습니다]
# 2️⃣ Command (커맨드패턴 / 액션 / 트랜잭션)
커맨드 패턴은 어떤 객체로 보내는 요청을 캡슐화 하는 패턴입니다.
요청을 보내고/받는 객체와 상관없이 **요청 자체를 객체로 캡슐화** 합니다.
그러면 다양한 요청들이 필요한 메서드들에 객체로 넘겨줄 수 있으며, 요청의 실행을 지연 또는 대기열에 넣을 수 있도록 하고, 실행 취소할 수 있는 작업을 만들수도 있습니다.
## 🔺 예시
서로 다른 작업을 하는 여러개의 버튼을 만들어야 한다고 가정해봅시다.
우선 재사용이 가능한 아주 깔끔한 `Button클래스`를 만들고,
이 Button Class의 객체들은 버튼이 눌렸을 때, 각자 다른 기능을 가져야합니다.
이럴 때 버튼 클래스에 요청 자체를 캡슐화해서 넘겨주는 Command패턴을 사용할 수 있습니다.
<img width = 200, src = "https://i.imgur.com/y2RNFGX.png">
## 🔎 구조
커맨드 패턴을 이해하기 위한 커맨드 패턴 구성요소이다
<img width = 400, src = "https://i.imgur.com/D2vYvF6.png">
1. `Commad 프로토콜`에 실행될 기능을 execute 메서드로 선언합니다
2. `ConcreteCommand 클래스`는 `Commad 프로토콜`을 채택하고, execute메서드에 실제로 실행되야할 기능을 구현합니다.
3. `Invoker 클래스`는 기능 실행을 요청하는 호출자 클래스입니다. (여기서는 Button클래스가 되겠네요)
4. `Receiver 클래스`는 ConcreteCommand의 기능을 실행하기 위해 사용하는 수신자 클래스입니다.
## 🔎 예제 살펴보기
#### `Commad 프로토콜` - `execute()` 선언
```swift
protocol Command {
func execute()
}
```
#### `Button Class`
- `command` 프로퍼티를 갖는다
- 버튼이눌릴때(`pressed()`) command의 `execute()`메서드를 호출한다
```swift
class Button {
private var command: Command
init(command: Command) {
self.command = command
}
func setCommand(command: Command) {
self.command = command
}
func pressed() {
self.command.execute()
}
}
```
#### `AlarmCommand`, `FlashOnCommand`
- Command프로토콜을 채택해서, 알림기능과 플래시켜는 기능을 Command 요구사항으로 구현한다.
```swift
class Alarm {
func start() {
print("Alarm Start 🔴⏰🔴")
}
}
class AlarmCommand: Command {
private var alarm: Alarm
init(alarm: Alarm) {
self.alarm = alarm
}
func execute() {
self.alarm.start()
}
}
```
```swift
class Flash {
func turnOn() {
print("Flash On🟡🔦🟡")
}
}
class FlashOnCommand: Command {
private var flash: Flash
init(flash: Flash) {
self.flash = flash
}
func execute() {
self.flash.turnOn()
}
}
```
#### 객체생성 후 버튼의 기능으로 전달
command 객체들을 생성해 버튼의 기능으로 주기위해 매개변수로 전달합니다.
```swift
let alarm = Alarm()
let alarmCommand = AlarmCommand(alarm: alarm)
let flash = Flash()
let flashOnCommand = FlashOnCommand(flash: flash)
let button1 = Button(command: alarmCommand)
let button2 = Button(command: flashOnCommand)
```
그 후 버튼들의 pressed()메서드를 호출합니다.
```swift
button1.pressed() // Alarm Start 🔴⏰🔴
button2.pressed() // Flash On🟡🔦🟡
button2.setCommand(command: alarmCommand)
button2.pressed() // Alarm Start 🔴⏰🔴
```
이렇게 하면, 다른 기능들을 추가하고 싶을 때, ButtonClass를 재사용하면서 기존코드 변경없이 새로운 버튼을 생성할 수 있습니다.
### ✅ 결론
- 작업,기능들을 객체로만드는게 유용한 경우 커맨드 패턴을 사용하세요.
- 런타임에 연결된 커맨드를 전환할 수 있습니다.
- 작업들의 실행을 예약하거나, 작업들을 대기열에 넣거나 작업들을 원격으로 실행하려는 경우에 사용하세요.
- 되돌릴 수 있는 작업을 구현하려고 할 때 사용하세요.(실행 취소/다시 실행)
- **장점**
- 단일 책임 원칙을 지킬 수 있습니다. 작업을 호출하는 클래스들을 작업을 수행하는 클래스들로부터 분리할 수 있습니다
- 개방/폐쇄 원칙을 지킬 수 있습니다. 기존 코드를 수정하지 않고 새로운 기능들을 추가할 수 있습니다.
- 실행 취소/다시 실행을 구현할 수 있습니다.
- 작업들의 지연된 실행을 구현할 수 있습니다.
- 간단한 커맨드들의 집합을 복잡한 커맨드로 조합할 수 있습니다.
- **단점**
- 발송자와 수신자 사이에 완전히 새로운 타입들이 생기는 것이므로, 코드가 더 복잡해질 수 있습니다.
# 커맨드 패턴과 책임연쇄 패턴의 관계
**책임 연쇄 패턴**의 핸들러들은 **커맨드**로 구현할 수 있습니다. 그러면 많고 다양한 작업을 같은 콘텍스트 객체에 대해 실행할 수 있으며, 해당 콘텍스트 객체는 요청을 뜻하는 매개변수의 역할을 합니다.
그러나 요청 자체가 커맨드 객체인 다른 접근 방식이 있습니다. 이 접근 방식을 사용하면 같은 작업을 체인에 연결된 일련의 서로 다른 콘텍스트들에서 실행할 수 있습니다.
---
# 3️⃣ Interpreter
사람이 작성한 코드를 하드웨어가 이해할 수 있도록 변환해주는 장치를 인터프리터라고 한다. 이것을 코드적으로 보았을 때, 반복되는 문제 패턴을 언어 또는 문법으로 정의하고 확장하는 것이다.
**즉, 문법적 규칙을 클래스화 하여 일련의 규칙을 통해 언어/문법을 해석하는 디자인 패턴**이다.
## 🔎 구조


- **Client**
- Interpreter 작업을 요청합니다.
- **Context**
- 모든 Expression에서 사용하는 공통된 정보가 담겨있습니다.
- 즉, Interpreter가 해석할 문장과 이와 관련된 코드
- **Abstract Expression**
- 해석하는 코드인 TerminalExpression 과 NonterminalExpression에서 사용할 Method를 제공하는 Protocol
- 프로젝트에서 사용할 Interpret 작업을 정의하는 곳
- **TerminalExpression**
- 그 자체로 종료되는 Expression
- 해석을 하는 독립적인 Code입니다.
- **NonTerminalExpression**
- 하나 이상의 TerminalExpression을 재귀적으로 참조하여 해석을 하는 Expression
## ⚙️ 협력 방법
- 사용자는 NonterminalExpression과 TerminalExpression 인스턴스들로 해당 문장에 대한 추상 구문 트리를 만듭니다. 그리고 사용자는 `Interpret()` 연산을 호출하는데, 이 때 해석에 필요한 Context 정보를 초기화 합니다.
- 각 NonterminalExpression 노드는 또 다른 서브 표현식에 대한 `Interpret()`을 이용하여 자신의 `Interpret()`을 정의합니다. `Interpret()`연산은 재귀적으로 `Interpret()`연산을 이용하여 기본적 처리를 담당합니다.
- 각 노드에 정의한 `Interpret()`연산은 해석자의 상태를 저장하거나 그것을 알기 위해서 Context 정보를 이용합니다.
## 🔺 예시
- SQL문에서 `SELECT (A) FROM (B) WHERE (Condition)`과 같은 문법을 지키면 인터프리터를 통해 쿼리문이 해석되고, 우리가 원하는 결과를 얻을 수 있게 되는 경우입니다.
- 예제 코드는 계산기 프로젝트때와 같이 `4 + 4 + 5 + 10 =`의 순서로 버튼을 클릭했을 때를 Interpreter 패턴을 활용해 작성해본 것입니다.
**AbstractExpression 정의**
TerminalExpression과 NonterminalExpression에서 준수해야할 메서드를 제공합니다.
```swift
protocol Expression {
func interpret() -> Int
}
```
**TerminalExpression**
독립적으로 `interpret()`을 진행하는 타입입니다.
```swift
class Number: Expression {
private let number: Int
init(number: Int) {
self.number = number
}
func interpret() -> Int {
return number
}
}
```
**NonterminalExpression**
`leftOperand` 와 `rightOperand`라는 TerminalExpression을 참조하고, 참조된 값의 재귀적인 `interpret()`을 호출해 본인의 `interpret()`을 진행하는 타입입니다.
```swift
class Addition: Expression {
private let leftOperand: Expression
private let rightOperand: Expression
init(leftOperand: Expression, rightOperand: Expression) {
self.leftOperand = leftOperand
self.rightOperand = rightOperand
}
func interpret() -> Int {
return leftOperand.interpret() + rightOperand.interpret()
}
}
```
Queue에 쌓인 연산자와 피연산자 데이터를 가지고 연산을 수행하는 타입입니다.
```swift
class Interpreter {
func interpret(with problem: String) -> Int {
var tokens: [Expression] = []
let tokenList = problem.components(separatedBy: " ")
for token in tokenList {
if token == "+" {
let leftOperand = tokens.remove(at: 0)
let rightOperand = tokens.remove(at: 0)
let result = Addition(leftOperand: leftOperand, rightOperand: rightOperand).interpret()
tokens.insert(Number(number: result), at: 0)
} else {
let num = Number(number: Int(token)!)
tokens.insert(num, at: 0)
}
}
let result = tokens.remove(at: 0)
return result.interpret()
}
}
```
**실행 결과**
```swift
let calculator = Interpreter()
let result = calculator.interpret(with: "4 4 + 5 10 + +")
print(result) // 23
```
- Note: `result` 변수에서 작성된 `String` 인자는 후위연산 형식입니다!
계산기 프로젝트와 같이 `피연산자 + 연산자` 데이터를 Queue(Context)에 쌓아두고,
`=` 버튼을 눌렀을 때 Queue에 쌓인 데이터을 가져왔다고 생각하면 됩니다.
e.g.) `4 + 4 =` 을 눌렀을 때, 피연산자의 0번 인덱스를 두번 가져오고(`dequeue`를 2번 실행하고) 연산자의 0번 인덱스를 가져와(`dequeue`를 1번 실행해) 결과값을 계산하는 것
## ✅ 결론
### 장점
- 문법 규칙들을 객체화하기 때문에 변경 및 확장이 쉽습니다.
- 인터프리터 패턴은 클래스를 사용하여 문법 규칙을 나타내기 때문에 상속을 사용하여 문법을 변경하거나 확장 할 수 있습니다.
- 문법을 구현하는 것도 쉽습니다.
- Abstract Syntax Tree에서 노드를 정의하는 클래스는 비슷한 구현부를 가지고 있습니다. 따라서 이러한 클래스를 만들기 쉽고 Compiler, Parser를 사용하여 생성을 자동화할 수 있습니다.
- 어떤 표현을 해석하는 새로운 방법을 쉽게 추가할 수 있습니다.
### 단점
- 인터프리터 패턴은 문법의 모든 규칙에 대해 하나 이상의 클래스를 정의합니다. 따라서 많은 규칙을 갖고 있는 문법은 관리, 유지가 어려울 수 있습니다. 따라서 규칙이 많이 복잡한 경우에는 컴파일러나 파서를 사용하는 것이 더 적합할 수 있습니다.
#### 참고: https://velog.io/@hoit_98/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-Interpreter-%ED%8C%A8%ED%84%B4
---