안녕하세요 비비 @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를 입력하면 프로그램을 종료합니다. ### 실행 예 ![](https://i.imgur.com/Pf8VI4s.png) # 📝 객체별 역할과 책임 | 객체 | 역할과 책임 | | ---------- | --------------------------------------------------------------------------------------------- | | 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스러운 코드 작성을 위한" 이라는 저희의 판단 기준이 적절할까요?