차량 대여 서비스
Coddy Java 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 87개 중 87번째.
이번 마지막 챌린지에서는 이 코스 전반에 걸쳐 마스터한 모든 OOP 개념을 하나로 모으는 차량 대여 서비스(Vehicle Rental Service) 시스템을 구축하게 됩니다.
시스템은 다양한 차량 유형의 집합을 모델링해야 합니다. 번호판, 브랜드, 일일 대여료와 같은 공통 속성을 가진 기본 Vehicle 클래스를 고려해 보세요. Car, Motorcycle, Truck과 같은 서브클래스는 좌석 수, 엔진 크기 또는 화물 용량과 같은 특정 속성으로 이를 확장할 수 있습니다.
인터페이스는 대여 동작을 정의할 수 있습니다: Rentable 인터페이스는 rent(), returnVehicle(), 그리고 calculateCost(int days)와 같은 메서드를 선언할 수 있습니다. 서로 다른 차량 유형은 가격 책정 전략을 다르게 구현할 수 있으며, 이는 다형성을 위한 완벽한 사용 사례가 됩니다.
RentalAgency 클래스가 컴포지션을 사용하여 차량 함대(fleet)를 관리하는 방식에 대해 생각해 보세요.
VehicleNotAvailableException과 같은 사용자 정의 예외는 예외적인 상황(edge cases)을 처리할 수 있습니다.
차량을 생성하기 위해 팩토리 패턴(Factory pattern)을 적용하거나, 차량 카테고리 및 대여 상태를 위해 열거형(enums)을 사용할 수 있습니다.
이 챌린지는 완전하고 잘 구조화된 OOP 시스템을 설계하는 능력을 테스트합니다. 행운을 빕니다!
챌린지
쉬움지금까지 배운 OOP의 모든 개념을 하나로 모아 완벽한 차량 대여 서비스(Vehicle Rental Service)를 구축해 봅시다! 다양한 차량 유형의 플릿을 관리하고, 적절한 검증을 통해 대여를 처리하며, 팩토리 패턴을 사용하여 차량을 생성하는 시스템을 만들게 됩니다.
코드는 다음 일곱 개의 파일로 구성됩니다:
RentalStatus.java: 차량의 대여 상태를 나타내는 enum을 생성합니다.AVAILABLE,RENTED,MAINTENANCE값을 포함하세요. private 필드description(String), 생성자, 그리고 getter를 추가합니다. AVAILABLE은 "ready to rent", RENTED는 "currently in use", MAINTENANCE는 "under service"라는 설명을 가져야 합니다.VehicleNotAvailableException.java:Exception을 상속받는 사용자 정의 예외를 생성합니다. 생성자는 번호판(license plate)을 인자로 받아Vehicle not available: [licensePlate]형식의 메시지를 생성해야 합니다.Rentable.java: 대여 작업을 정의하는 인터페이스를 생성합니다. boolean을 반환하는rent(), boolean을 반환하는returnVehicle(), 그리고 double을 반환하는calculateCost(int days)메서드를 선언하세요.Vehicle.java: 모든 차량의 추상 기본 클래스를 생성합니다.licensePlate(String),brand(String),dailyRate(double),status(RentalStatus, AVAILABLE로 초기화)를 위한 private 필드를 포함합니다. 생성자는 licensePlate, brand, dailyRate를 인자로 받습니다. 적절한 getter들과 status를 위한 setter를 포함하세요. String을 반환하는 추상 메서드getVehicleType()을 선언합니다."[licensePlate] [brand] ([vehicleType]) - $[dailyRate]/day - [status description]"을 반환하는getDetails()메서드를 구현하며, dailyRate는 소수점 2자리까지 포맷팅해야 합니다.Car.java: Vehicle을 상속받고 Rentable을 구현하는 클래스를 생성합니다. private 필드seats(int)를 추가합니다. 생성자는 licensePlate, brand, dailyRate, seats를 인자로 받습니다.getVehicleType()이"Car"를 반환하도록 구현합니다.rent()의 경우, status가 AVAILABLE이면 RENTED로 설정하고 true를 반환하며, 그렇지 않으면 false를 반환합니다.returnVehicle()의 경우, status가 RENTED이면 AVAILABLE로 설정하고 true를 반환하며, 그렇지 않으면 false를 반환합니다.calculateCost(int days)는 dailyRate에 days를 곱한 값을 반환합니다.Motorcycle.java: Vehicle을 상속받고 Rentable을 구현하는 클래스를 생성합니다. private 필드engineCC(int)를 추가합니다. 생성자는 licensePlate, brand, dailyRate, engineCC를 인자로 받습니다.getVehicleType()이"Motorcycle"을 반환하도록 구현합니다. Rentable 메서드들은 Car와 동일하게 구현하되,calculateCost()는 10% 할인을 적용해야 합니다(0.9를 곱함).RentalAgency.java: ArrayList를 사용하여 차량 플릿을 관리하는 클래스를 생성합니다. 다음 메서드들을 포함하세요:addVehicle(Vehicle vehicle)- 플릿에 차량을 추가합니다.findVehicle(String licensePlate)- 해당 차량을 반환하거나 없으면 null을 반환합니다.rentVehicle(String licensePlate, int days)- 차량을 찾고, 대여를 시도하며(Rentable로 캐스팅), 대여 비용을 반환합니다. 차량을 찾을 수 없거나 rent()가 false를 반환하면VehicleNotAvailableException을 던집니다.returnVehicle(String licensePlate)- 차량을 찾아 반납하며, 성공 시 true를 반환합니다.getAvailableVehicles()- AVAILABLE 상태인 차량들의 ArrayList를 반환합니다.
Main.java: 대여 서비스를 구축하세요! 쉼표로 구분된 명령어 문자열을 입력받게 됩니다:ADD_CAR:licensePlate:brand:dailyRate:seatsADD_MOTORCYCLE:licensePlate:brand:dailyRate:engineCCRENT:licensePlate:daysRETURN:licensePlateAVAILABLE
각 명령어를 처리하고 다음을 출력하세요:
- ADD_CAR/ADD_MOTORCYCLE:
Added: [brand] ([vehicleType]) - RENT:
Rented [licensePlate] for [days] days - Total: $[cost](비용은 소수점 2자리까지), 또는Error: [exception message] - RETURN:
Returned: [licensePlate]또는Return failed: [licensePlate] - AVAILABLE:
Available vehicles:를 출력한 후, 각 사용 가능한 차량의 상세 정보를 별도의 줄에 출력합니다.
모든 명령어가 끝나면,
--- Fleet Summary ---를 출력한 후 각 차량의 상세 정보를 출력합니다.
하나의 입력을 받게 됩니다: 쉼표로 구분된 명령어 문자열입니다.
예를 들어, 입력이 ADD_CAR:ABC-123:Toyota:50.00:5,ADD_MOTORCYCLE:XYZ-789:Honda:40.00:600,RENT:ABC-123:3,RENT:ABC-123:2,AVAILABLE,RETURN:ABC-123인 경우, 출력은 다음과 같습니다:
Added: Toyota (Car)
Added: Honda (Motorcycle)
Rented ABC-123 for 3 days - Total: $150.00
Error: Vehicle not available: ABC-123
Available vehicles:
XYZ-789 Honda (Motorcycle) - $40.00/day - ready to rent
Returned: ABC-123
--- Fleet Summary ---
ABC-123 Toyota (Car) - $50.00/day - ready to rent
XYZ-789 Honda (Motorcycle) - $40.00/day - ready to rent이 챌린지는 상속(Vehicle 계층 구조), 인터페이스(Rentable), enum(RentalStatus), 사용자 정의 예외, 다형성(다양한 차량 유형을 균일하게 처리), 그리고 구성(RentalAgency가 차량을 관리함)을 결합하여 완전한 대여 서비스 시스템을 만드는 과정입니다!
치트 시트
이 마지막 챌린지는 여러 OOP 개념을 하나의 완성된 시스템으로 결합합니다. 주요 패턴과 기술은 다음과 같습니다:
필드가 있는 열거형(Enums)
열거형은 필드, 생성자 및 메서드를 가질 수 있습니다:
public enum RentalStatus {
AVAILABLE("ready to rent"),
RENTED("currently in use");
private String description;
private RentalStatus(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}사용자 정의 예외(Custom Exceptions)
Exception을 확장하여 도메인별 예외를 생성합니다:
public class VehicleNotAvailableException extends Exception {
public VehicleNotAvailableException(String licensePlate) {
super("Vehicle not available: " + licensePlate);
}
}인터페이스를 포함한 추상 기본 클래스
공유 구현을 위한 추상 클래스와 동작 계약을 위한 인터페이스를 결합합니다:
public abstract class Vehicle {
private String licensePlate;
private RentalStatus status;
public abstract String getVehicleType();
public String getDetails() {
return licensePlate + " " + getVehicleType() + " - " + status.getDescription();
}
}
public interface Rentable {
boolean rent();
boolean returnVehicle();
double calculateCost(int days);
}
public class Car extends Vehicle implements Rentable {
public boolean rent() {
if (getStatus() == RentalStatus.AVAILABLE) {
setStatus(RentalStatus.RENTED);
return true;
}
return false;
}
}관리를 위한 구성(Composition)
구성을 사용하여 객체 컬렉션을 관리합니다:
public class RentalAgency {
private ArrayList<Vehicle> fleet = new ArrayList<>();
public void addVehicle(Vehicle vehicle) {
fleet.add(vehicle);
}
public double rentVehicle(String licensePlate, int days) throws VehicleNotAvailableException {
Vehicle vehicle = findVehicle(licensePlate);
if (vehicle == null) {
throw new VehicleNotAvailableException(licensePlate);
}
Rentable rentable = (Rentable) vehicle;
if (!rentable.rent()) {
throw new VehicleNotAvailableException(licensePlate);
}
return rentable.calculateCost(days);
}
}다형성의 활용
서로 다른 서브클래스는 인터페이스 메서드를 다르게 구현할 수 있습니다:
// Car: 표준 가격 책정
public double calculateCost(int days) {
return dailyRate * days;
}
// Motorcycle: 할인된 가격 책정
public double calculateCost(int days) {
return dailyRate * days * 0.9;
}직접 해보기
import java.util.Scanner;
import java.util.ArrayList;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
// TODO: RentalAgency 인스턴스 생성
// TODO: 입력을 쉼표로 분할하여 개별 명령어로 나누기
// TODO: 각 명령어 처리:
// ADD_CAR:licensePlate:brand:dailyRate:seats -> "Added: [brand] ([vehicleType])" 출력
// ADD_MOTORCYCLE:licensePlate:brand:dailyRate:engineCC -> "Added: [brand] ([vehicleType])" 출력
// RENT:licensePlate:days -> "Rented [licensePlate] for [days] days - Total: $[cost]" 출력 (소수점 2자리)
// 또는 예외가 발생한 경우 "Error: [exception message]" 출력
// RETURN:licensePlate -> "Returned: [licensePlate]" 또는 "Return failed: [licensePlate]" 출력
// AVAILABLE -> "Available vehicles:" 출력 후 각 대여 가능한 차량의 상세 정보 출력
// TODO: 모든 명령어를 처리한 후, "--- Fleet Summary ---" 출력
// 이어서 각 차량의 상세 정보 출력
}
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
4상속
상속의 기초 (extends)super 키워드메서드 오버라이딩 (@Override)생성자 체이닝Object 클래스단일 및 다중 레벨 상속다중 클래스 상속이 불가능한 이유요약 - 직원 계층 구조