본문 바로가기

공부/TIL

호텔 예약 프로그램

자바 팀프로젝트로 진행했던 호텔 관리 프로그램을 완성했다. 어떻게 진행됐고, 어떻게 구현됐는지에 대해 기록하는 글.

 

프로그램 요구사항은 다음과 같다.

  1. 호텔은 여러 객실, 보유 자산을 가지고 있다.
  2. 객실은 객실 당 하루에 한 사람만 예약이 가능하다.
  3. 객실은 크기, 숙박비를 가진다.
  4. 예약은 객실, 고객의 이름, 고객의 전화번호, 예약 날짜를 가지고 있다.
    1. 전화 번호 제한(XXX-XXXX-XXXX) 정규 표현식 (선택)
    2. 예약 날짜 ****
    3. 날짜는 ISO 8601 형식으로 조합된 UTC 날짜 및 시간 예) 2016-10-27T17:13:40+00:00
  5. 고객은 이름, 전화번호, 소지금을 가진다.
    1. 고객 소지금보다 비싼 방은 예약 불가
  6. 호텔은 모든 예약 목록을 조회 할 수 있다.
  7. 고객은 자신의 예약 목록을 조회 할 수 있다.
    1. 예약 번호로 예약 내역을 조회한다
  8. 고객은 자신의 예약을 취소 할 수 있다.
  9. 고객이 호텔 예약 시에 예약 번호(id)를 반환 (uuid 활용)
    1. 고객이 호텔 예약에 성공하면 예약 번호(id)를 받는다.
    2. 고객이 예약 목록을 조회 시 예약 번호도 같이 조회 된다.
    3. 고객이 예약 취소 시 예약 번호를 통해 자신의 특정 예약을 취소한다.

프로젝트 요구사항에 맞춰 최종적으로 설계된 프로그램의 플로우 차트는 다음과 같다.(플로우차트 쓰는법 좀 더 공부해야겠다.)

호텔 예약 프로그램의 전체 플로우 차트

전체 흐름에 맞춰 각 기능들을 수행할 클래스들을 나누고, 설계 했다. 전체 설계된 클래스의 구성은 Hotel 클래스, Room 클래스, Customer 클래스, Reservation 클래스, View 클래스 5개의 클래스로 나뉘어졌다. 각 클래스가 갖는 필드/메소드는 다음과 같이 구성된다.

프로그램 클래스 다이어그램

[Reservation 클래스]

가장 많은 역할을 해야 하는건 역시 Reservation클래스다. Reservation 클래스는 이름 대로 예약을 하는 로직을 담고 있다.

Reservation 클래스엔 Room, Customer, Date, 예약번호를 담을 String 타입의 필드값을 갖는다. 

Reservation 클래스의 makeReservation 메서드는 예약하는 방, 고객, 날짜 정보를 담아야 하기에 3가지를 모두 인자를 받는 메서드다. 또한 우리는 예약을 한개의 객체로서 보기 위해 메서드의 반환값으로 Reservation 인스턴스를 반환하도록 설계했다. makeReservation의 기능은 두가지 조건이 충족시 예약 성공 한다. 예약이 성공하는 두가지 조건은 1) 손님의 예산이 예약 방의 가격보다 더 많을 때. 2) 예약 날짜에 예약 방이 예약이 가능한 상태일 때, 인자로 받은 Room, Customer, Date 정보를 필드값으로 갖는 Reservation 인스턴스를 하나 반환한다. 이때 예약이성공할 대 View 파트에서 UUID를 출력해 예약 번호를 보여주게 했다. 만약 두 조건중 하나라도 만족하지 않는다면, 예약이 실패하고 null을 반환하게 된다.

이렇게 makeReservation메서드를 통해 만들어진 예약 객체는 후에 설명할 Hotel 클래스의 addReservation 메서드의 인자로서 들어가게 설계했다. Reservation 클래스의 나머지 메서드들은 각 객체의 정보값들을 얻기 위한 get메서드이다. 이유는 클래스 안의 모든 필드값들은 private으로 설정됐기 때문이다. 

 

[Customer 클래스]

고객 클래스에는 고객의 이름, 고객의 전화번호, 고객의 가용 예산을 넣었다. 가용 예산은 방을 예약할 때 조건에 사용되는 데이터이다. 처음에 Customer 클래스에 예약번호를 넣어주는 필드값 사용했다가 삭제했다. 우리가 작성한 것은 호텔의 예약 프로그램인데, 호텔 입장에서 고객 객체가 예약번호를 갖고 있는것이 적절한가?생각을 해봤다. 결과는 예약 번호는 호텔이 갖고 있어야지 고객이 갖고 있을 데이터는 아니다. 라고 판단 하여 삭제 후, Hotel클래스가 갖도록 설계를 변경했다.

 

[Room 클래스]

Reservation에서 사용된 Date 값은 Room 클래스의 dateList에서 사용된다. Room 클래스는 기본적인 방의 정보를 담고 있다. 방의 크기, 방의 가격, 방 호수. 그래고 핵심적인 방의 예약 가능날짜를 담을 dateList이다.

이번 프로젝트를 진행하면서 내가 고민 했던 것은 10월 30일의 101호와 11월 1일의 101호는 같은 101호라는 아주 많은 공통된 정보를 갖지만, 서로 다른 상품이라는 것! 10월 30일의 101호가 예약 된 순간, 10월 30일의 101호는 예약 완료가 되어야 한다. 하지만 11월 1일의 101호는 아직 예약 가능해야한다. 이를 구현하기 위해 Room 객체를 여러개 생성해서 사용할 수 있지만 내생각엔 그건 불필요한 메모리를 사용한다고 생각했다. 나의 아이디어는 "dateList를 하나 만들어서 각각의 인덱스값을 하나의 새로운 객체로 치환해서 생각하면 된다!"였다. 따라서 방의 해당 날짜의 예약 가능상태를 알아보려면 방의 dateList의 해당 날짜가 존재하는지 list의 contains()메서드를 이용하여 조회한 후 존재한다면 예약이 불가능한 상태라고 알려주는 메서드를 이용했다. 이 매소드는 위의 makeReservation()메서드의 2)번 조건 방의 예약 가능 상태 확인에 사용된 메서드이다. 이후 예약이 가능하다면, addDateList()메서드를 통해 dateList에 날짜 인덱스를 추가한다.

 

[Hotel 클래스]

makeReservation을 통해 반환된 Reservation 인스턴스는 Hotel 클래스에서 사용된다. Hotel클래스는 전체적인 호텔 운영을 위한 필드값과 메서드를 이용한다. 우선 호텔이 가져야할 데이터는 호텔 방의 정보들(101호, 102호와 같은), 예약이 완료된 데이터, 호텔의 자산이다. 호텔은 예약이 성공할 때 반환된 Reservation 인스턴스를 reservationMap에 담는다. 이때 key는 예약시 출력된 예약번호 그리고 value값이 Reservation 인스턴스. 두 정보는 항상 짝을 이루어 존재하고, key값은 유니크 하므로 map의 자료구조를 사용했다. 이렇게 reservationMap의 정보가 들어오면, map의 데이터로 예약 관련 정보들을 처리한다.

대표적으로 getPossibleRoomList() 메서드이다.이 메서드는 Date를 인자를 받고, List<Room>을 반환하는 메서드이다. 인자로 받은 Date의 날짜에 예약 가능한 모든 방을 List에 add()하여 새로운 List<Room>(메서드 안의 변수)을 반환해주는 메서드이다. 이 메서드의 기능구현은 다음과 같이 했다. 호텔이 가지고 있는 List<Room>(호텔이 갖는 필드 변수)을 모두 도는 반복문에 인자로 받은 Date의 값이 존재여부를 판단하여 Date값이 존재하면 List<Room>에 추가하지 않고, 존재하지 않을때마다 추가하여 새로운 List<Room>을 반환해준다. 예약 가능한 방을 담고 있는 List를 Hotel의 필드값으로 포함시키지 않은 이유는 getPossibleRoomList외엔 사용하지 않기때문이다. 

또 고객이 예약확인을 할 때 사용된다. 예약 성공시 출력된 예약 번호를 입력하면 map의 키값을 받아온 것이므로 value값인 예약 인스턴스를 꺼내는 것은 쉽다. 이후 예약 인스턴스의 필드값인 방정보, 고객정보, 예약 날짜는 get메서드를 통해 가져올 수 있다

예약이 성공적으로 이루어지면 Hotel 클래스 내부의 예산이 증가하고, 예약Map에 정보를 추가한다.

반대로 예약이 취소될 경우 Hotel클래스의 내부 예산을 감소하고, 예약 Map의 정보를 삭제한다. 또한 Room의 dateList에서 해당 날짜 인덱스를 지워준다.그렇게 해야 getPossibleRoomList또한 나오지 않게 된다.

이렇게 호텔클래스는 전체적인 호텔의 업무를 볼 수 있는 종합적인 클래스로서 설계 했다.

 

[View 클래스]

View클래스는 다른 팀원 두명이 작업한 클래스이다. View클래스는 사용자에게 보여줄 출력 화면과 사용자가 입력하는 입력 파트를 우리가 작성한 예약 로직에 맞춰서 유기적으로 작동되게 구현됐다. View클래스는 위에 flowchart의 흐름을 통해 사용자가 입력하고 출력 된 화면으로 확인 할 수 있도록 구현됐다.

 

[개선방안 및 배운 점]

사실 내 생각에 기능적으로 지금의 기능 구현이라면 최선이라고 생각한다. 기능적 측면에서 이 프로젝트에 추가되어야 할 기능이 있을까? 생각하면 잘 떠오르지 않는다. 하지만 구현할 때 필요없는 메서드들이 존재한다. 그리고 그 메서드들은 보통 내가 만들었다. 코드를 보면 한줄짜리 메서드들이 꽤 많이 나왔다. 그 이유는 내가 생각하기에 한줄짜리 메서드라도 이름을 내가 지어줘야 나중에 코드 해석을 할 때 좀 더 가독성이 높아 질것이라 생각했다. 하지만 팀원들이 이런 메서드들은 깔끔하게 사용하기 위해 기존에 자바에서 제공하는 메서드들을 그냥 그대로 사용 하면 된다고 했다. 나도 그말에 동의한다. 팀프로젝트가 종료되고 튜터님께 질문을 하러 갔다. 짧은 메서드라도 새로 만들어서 사용하는 게 나은지? 질문했고 답은 재사용성. 이번 프로젝트에는 재사용성이 높은 메서드들이 많이 등장하지 않았지만, 실무에 가면 재사용되어야 할 메서드들이 정말 많아진다고 했다. 따라서 재사용이 자주 되는 메서드들은 짧아도 묶어서 사용할 수 있도록 하는 게 좋다고 했다.나도 앞으로는 재사용성이 높은 기능들은 메서드로 만들어서 사용하고 그렇지 않은 기능들은 우선은 그냥 사용하기로 했다.

위에도 설명했지만 생각보다 설계를 얼마나 타이트하게 하느냐가 중요한 것 같다. 설계가 타이트하다면 기능구현은 사실 그렇게 오래 걸리지 않았다. 오히려 설계미스가 나서 "이 설계가 왜 나는 이해가 안되지? 다른팀원들은 이해가 돼서 잘 하고 있나..?"라는 의문을 갖게 했고 결과적으로 다른 팀원들도 나와 같은 고민을 해서 회의시간에 다같이 설계를 수정해서 다시 진행했다. 이번에 핵심적인 설계 변형은 Room클래스와 Reservation클래스였다. 나는 위에 설명했듯 dateList가 Room에 있어야 한다고 주장했지만, 내 의견피력 실력이 부족해 팀원들이 잘못이해했고, Room에 있어야할 dateList가 Reservation 클래스로 들어가버렸다! 우리의 설계에는 예약성공 시 예약 객체는 1개의 예약정보만 담아야 하기에 Reservation 클래스엔 Date값이 1개만 필요하다. 따라서 List로서 존재하면 안된다. 물론 1개만 담는 리스트로 쓸 수 있겠지만 그건 틀린 프로그래밍이라고 생각한다. 회의를 통해 Room클래스로 dateList를 옮겨졌고 그 후 프로그램은 아주 빠르게 구현이 이루어졌다. 또한 기능 구현 파트와 View파트와의 애매한 분업이 초반 발목을 잡았다. View파트에서 분기를 나누어 전체 흐름을 만들어가는데 필요한 메서드들이 어떤 인자값을 받을지? 또 어떤 반환을 해서 어떻게 메서드와 클래스가 유기적으로 작동하는지 아직 토론이 안된 상태로 각자의 길을 갔었다 그래서 첫 하루는 정말 더딘 속도로 진행됐었다.

다음부터는 아예 처음부터 팀원들과 같이 흐름이 어떻게 진행될지, 해당 메서드들은 어떻게 구성되는지 대충이라도 얘기를 하고 시작하자고 했다. 아직 팀프로젝트가 다들 경험이 적어 좌충우돌이 있었지만 그래도 잘 해냈다고 생각한다.

그리고 깃허브... 정말 너무 어렵지만 오늘 시간이 남아 깃허브를 잘하는 팀원이 특강을 해줬다. 이제 나도 팀장으로서 깃허브를 많이 만져봤기에 이제 깃은 두렵지 않다. 하지만 아직 갈길은 멀다고 생각한다.

'공부 > TIL' 카테고리의 다른 글

23.11.01  (0) 2023.11.01
23.10.31  (1) 2023.10.31
23.10.27  (0) 2023.10.27
호텔 예약 프로그램  (1) 2023.10.26
23.10.26  (1) 2023.10.26