안녕하세요 비비 @YebinKim 🙇♂️ Mason, iyeah 입니다!
활기찬 월요일 보내고 계신가요?! ✨
Step 2는 타입과 콘솔앱을 구현해야 하는 요구사항이 있었는데요,
저희는 객체별 역할과 책임에 대해 깊이 고려하여 타입을 구현하고,
콘솔앱 또한 몇 번의 리팩토링을 거쳐 최선을 다해 구현해보았어요 🫡
특히, 네이밍에 대한 고민도 많았어서 너무 길거나 불필요한 부분이 있다면 알려주세요!
부족한 점이 있다면 아낌없는 피드백 부탁드릴게요 항상 감사합니다 🍀
# ✅ 요구사항 & 체크리스트
### 타입 구현 - 은행, 고객
- [x] 은행에는 n명의 은행원이 근무합니다.
- [x] 은행에는 n명의 고객이 업무처리를 위해 대기합니다
- [x] 고객의 대기열은 Step 1에서 구현한 Queue 타입을 활용합니다
- [x] 모든 고객의 업무가 끝나면 은행업무를 마감합니다
- [x] 업무를 마감할 때 "업무가 마감되었습니다. 오늘 업무를 처리한 고객은 총 XX명이며, 총 업무시간은 XX초입니다."라고 출력합니다
- [x] 은행원은 고객의 업무를 처리합니다
- [x] 각 고객의 업무를 처리하는 데 걸리는 시간은 0.7초입니다
- [x] 은행원이 한 번에 처리할 수 있는 고객은 한 명입니다
- [x] 대기중인 고객의 업무처리를 시작할 때 "3번 고객 업무 시작"과 같이 출력합니다
- [x] 고객의 업무를 처리하면 "5번 고객 업무 완료" 와 같이 출력합니다
### 콘솔앱 구현
- [x] Step 2의 은행에는 한 명의 은행원이 근무하며, 한 번에 한 명의 고객의 업무를 처리할 수 있습니다.
- [x] 앱을 실행하면 두 개의 메뉴를 출력합니다. `1 : 은행 개점 / 2 : 종료`
- [x] 사용자가 1을 입력하면 은행을 개점하고 10명~30명의 고객이 방문합니다. 10~30 사이의 임의의 수 만큼의 고객의 업무를 처리하면 은행문이 닫히고 다시 메뉴를 출력합니다.
- [x] 사용자가 2를 입력하면 프로그램을 종료합니다.
### 실행 예

# 📝 객체별 역할과 책임
| 객체 | 역할과 책임 |
| ---------- | --------------------------------------------------------------------------------------------- |
| LinkedList | Queue 구현을 위한 LinkedList 자료구조 타입 |
| Queue | Queue를 구현하기 위한 자료구조 타입 <br> (은행에 도착한 고객이 임시로 대기할 대기열에서 사용) |
| Customer | 은행을 방문하는 고객<br>각자 업무에 몇초의 시간이 소요되는지에 대한 정보를 갖고있음 |
| BankTeller | 고객의 업무를 처리하는 은행원 |
| Bank | 고객의 대기열을 관리하고, 업무를 처리할 은행원을 관리하는 은행 객체 |
| BankManager | 은행 객체를 관리하는 매니저<br>(랜덤한 고객 수 생성, 은행 개점...) |
| ConsoleManager | 사용자 콘솔에 `print` 출력을 하는 역할 |
# **⁉️ 고민과 해결**
### 1️⃣ 객체간의 역할
* 저희는 객체간의 역할에 대해 충분한 시간을 두고 오래 고민했습니다.
* `BankTeller`가 고객의 업무를 처리하기 때문에 고객의 대기열 또한 가지고 있어야 한다고 생각하여 구현하였다가, 은행 타입 구현에서 `은행에는 n명의 고객이 업무처리를 위해 대기합니다`를 참고하여 은행이 대기열을 가져야 한다고 판단하여 최종 수정하였습니다.
* 또한, 각 역할에 따라 고객 - 은행원 - 은행 이라는 객체가 각각 분리되는 것이 좋을 것 같다는 생각이 들었습니다.
>🤔 저희가 열심히 고민하여 구현하였지만 객체간의 역할과 책임이 적절하게 분배 된 것인지에 대해 븨븨의 의견이 궁금합니다..!
> (+ `Bank` 와 `BankManager` 객체를 분리한 것에 대해 과한 분리가 아닌가라는 생각도 들었습니다...😅)
### while문 for문으로 변경
* main에서 유저의 menu 입력을 받는 `excute()` 함수를 while문으로 작성하였다가, 아무리 로직을 잘 작성한다 하더라도 무한루프를 일으킬 수 있다는 while문의 기본 특성상 안전한 코드는 아니라고 판단되어 for 문으로 변경하였습니다.
* 변경하는 과정에서 linkedList 의 `count` 속성을 구현 해 주어야 했는데요,
이 때 각각의 다음 노드를 탐색하며 갯수를 확인하기에 O(n)의 복잡도가 발생하였습니다.
> 각 `append`, `removeFirst`에서 count의 갯수를 변경 해 준다면 O(1)로 구현할 수도 있겠지만, 멀티스레드 환경에서 적절하지 않은 `count` 값을 리턴해줄 수 있을 것 같다고 판단했기에 실제 count 에 접근하는 그 순간에 계산하도록 구현했습니다.
- count를 구하는 과정에서 while 문을 사용하긴 하지만, 해당 구현은 LinkedList 내부의 구현이기에,
호출부에서 while문을 사용하는 것 보다는 안전할 것 같다고 판단하였습니다.
### 고차함수의 사용
- 아래와 같은 Bank를 고객들이 방문하는 함수를 구현할 때, forEach 사용과 for문 사용에 대해 고민하였습니다.
- 고차함수를 학습하며 "상태를 변경하지 않은 순수함수"로 구현하는 것이 좋다는 말을 듣게 되었는데,
>🤔 forEach도 고차함수이기에 for문을 써야하는지? 아래 예시같은 단순한 활용에서는 큰 상관이 없는지에 대해 궁금합니다..!
```swift
struct Bank {
mutating func visit(customers: [Customer]) {
customers.forEach {
customersQueue.enqueue($0)
}
}
}
```
### usleep vs Thread.sleep(forTimeInterval:)
- 요구사항인 0.7초간 딜레이를 주는 부분에 대해 처음엔 `usleep` 으로 구현을 하였었지만, 후에 `Thread.sleep(forTimeInterval:)` 이 있는 것을 알고 다른 점을 조사하였습니다.
- 공부 한 결과 `usleep` 은 Darwin 모듈에 속하는 Unix 운영체제에 좀 더 가까운 함수이고,
- [`Thread.sleep(forTimeInterval:)`](https://developer.apple.com/documentation/foundation/thread/1413673-sleep) 은 swift Foundation에 속하는 기본 함수임을 알게 되었습니다.
- 둘 다 비슷한 동작을 하지만, 좀 더 스위프트 스러운 코드를 작성하는 것이 더 가독성 부분에서 좋을 것이라는 판단이 들어 `Thread.sleep`으로 코드를 작성하였습니다.
> 🤔 "Swift스러운 코드 작성을 위한" 이라는 저희의 판단 기준이 적절할까요?