Based on the requirements and use cases, identify the main objects of the system and analyze how they interact and relate to each other...
Here's a Class Diagram illustrating the relationships of the core objects and too each other:
For each class, define the attributes (data) it will hold and the methods (functions) that operate on the attributes. Ensure they align with the object's responsibilities and adhere to the principle of encapsulation. Write your code in the code editor below.
import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
enum VehicleType {
MOTORCYCLE, COMPACT, REGULAR, LARGE, EV
}
enum ParkingSpotType {
MOTORCYCLE, COMPACT, REGULAR, LARGE, EV
}
enum TicketStatus {
OPEN, CLOSED, LOST
}
class Driver {
private String firstName;
private String lastName;
private int age;
private String email;
public Driver(String firstName, String lastName, int age, String email) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.email = email;
}
}
class Vehicle {
private String make;
private String model;
private int year;
private VehicleType vehicleType;
private Driver driver;
private String licensePlateNumber;
public Vehicle(String make, String model, int year, VehicleType vehicleType,
Driver driver, String licensePlateNumber) {
this.make = make;
this.model = model;
this.year = year;
this.vehicleType = vehicleType;
this.driver = driver;
this.licensePlateNumber = licensePlateNumber;
}
public VehicleType getVehicleType() {
return vehicleType;
}
public String getLicensePlateNumber() {
return licensePlateNumber;
}
}
class ParkingSpot {
private final int spotNum;
private final ParkingSpotType parkingSpotType;
private Vehicle vehicle;
private boolean assigned;
public ParkingSpot(int spotNum, ParkingSpotType parkingSpotType) {
this.spotNum = spotNum;
this.parkingSpotType = parkingSpotType;
this.assigned = false;
}
public synchronized boolean tryAssignVehicle(Vehicle vehicle) {
if (!canFitVehicle(vehicle)) {
return false;
}
this.vehicle = vehicle;
this.assigned = true;
return true;
}
public synchronized void removeVehicle() {
this.vehicle = null;
this.assigned = false;
}
public synchronized boolean canFitVehicle(Vehicle vehicle) {
if (assigned) return false;
VehicleType type = vehicle.getVehicleType();
return switch (parkingSpotType) {
case MOTORCYCLE -> type == VehicleType.MOTORCYCLE;
case COMPACT -> type == VehicleType.MOTORCYCLE || type == VehicleType.COMPACT;
case REGULAR -> type == VehicleType.MOTORCYCLE
|| type == VehicleType.COMPACT
|| type == VehicleType.REGULAR;
case LARGE -> type == VehicleType.MOTORCYCLE
|| type == VehicleType.COMPACT
|| type == VehicleType.REGULAR
|| type == VehicleType.LARGE;
case EV -> type == VehicleType.EV;
};
}
public int getSpotNum() {
return spotNum;
}
public ParkingSpotType getParkingSpotType() {
return parkingSpotType;
}
}
class ParkingFloor {
private final int floorNum;
private final List
public ParkingFloor(int floorNum) {
this.floorNum = floorNum;
this.spots = new ArrayList<>();
}
public void addSpot(ParkingSpot spot) {
spots.add(spot);
}
public Optional
for (ParkingSpot spot : spots) {
if (spot.tryAssignVehicle(vehicle)) {
return Optional.of(spot);
}
}
return Optional.empty();
}
public int getFloorNum() {
return floorNum;
}
}
class Ticket {
private final int ticketId;
private final LocalDateTime checkIn;
private LocalDateTime checkOut;
private TicketStatus status;
private BigDecimal fee;
private final ParkingSpot spot;
private final Vehicle vehicle;
public Ticket(int ticketId, ParkingSpot spot, Vehicle vehicle) {
this.ticketId = ticketId;
this.spot = spot;
this.vehicle = vehicle;
this.checkIn = LocalDateTime.now();
this.status = TicketStatus.OPEN;
this.fee = BigDecimal.ZERO;
}
public void close(BigDecimal fee) {
this.checkOut = LocalDateTime.now();
this.status = TicketStatus.CLOSED;
this.fee = fee;
this.spot.removeVehicle();
}
public int getTicketId() {
return ticketId;
}
public LocalDateTime getCheckIn() {
return checkIn;
}
public TicketStatus getStatus() {
return status;
}
public BigDecimal getFee() {
return fee;
}
public ParkingSpot getSpot() {
return spot;
}
public Vehicle getVehicle() {
return vehicle;
}
}
interface PricingStrategy {
BigDecimal calculate(Ticket ticket);
}
class DurationBasedPricingStrategy implements PricingStrategy {
@Override
public BigDecimal calculate(Ticket ticket) {
long minutes = Duration.between(ticket.getCheckIn(), LocalDateTime.now()).toMinutes();
long hours = (long) Math.ceil(minutes / 60.0);
if (hours <= 1) return BigDecimal.valueOf(1);
if (hours <= 3) return BigDecimal.valueOf(3);
if (hours <= 5) return BigDecimal.valueOf(6);
if (hours <= 9) return BigDecimal.valueOf(9);
return BigDecimal.valueOf(24);
}
}
class TicketService {
private final ConcurrentHashMap
private final Queue
private final PricingStrategy pricingStrategy;
private final AtomicInteger ticketIdGenerator = new AtomicInteger(1);
public TicketService(PricingStrategy pricingStrategy) {
this.pricingStrategy = pricingStrategy;
}
public Ticket createTicket(ConcurrentHashMap
for (ParkingFloor floor : floors.values()) {
Optional
if (spotOptional.isPresent()) {
ParkingSpot spot = spotOptional.get();
int ticketId = ticketIdGenerator.getAndIncrement();
Ticket ticket = new Ticket(ticketId, spot, vehicle);
tickets.put(ticketId, ticket);
return ticket;
}
}
waitList.add(vehicle);
throw new IllegalStateException("No available parking spots. Vehicle added to waitlist.");
}
public Ticket closeTicket(int ticketId) {
Ticket ticket = tickets.get(ticketId);
if (ticket == null) {
throw new IllegalArgumentException("Ticket not found.");
}
if (ticket.getStatus() != TicketStatus.OPEN) {
throw new IllegalStateException("Ticket is not open.");
}
BigDecimal fee = pricingStrategy.calculate(ticket);
ticket.close(fee);
alertSpotOpen();
return ticket;
}
private void alertSpotOpen() {
Vehicle nextVehicle = waitList.peek();
if (nextVehicle != null) {
System.out.println("Spot opened. Next vehicle in waitlist: "
+ nextVehicle.getLicensePlateNumber());
}
}
}
class ParkingGarage {
private final ConcurrentHashMap
private final TicketService ticketService;
public ParkingGarage(TicketService ticketService) {
this.ticketService = ticketService;
}
public void addFloor(ParkingFloor floor) {
floors.put(floor.getFloorNum(), floor);
}
public Ticket createTicket(Vehicle vehicle) {
return ticketService.createTicket(floors, vehicle);
}
public Ticket closeTicket(int ticketId) {
return ticketService.closeTicket(ticketId);
}
}
public class Main {
public static void main(String[] args) {
PricingStrategy pricingStrategy = new DurationBasedPricingStrategy();
TicketService ticketService = new TicketService(pricingStrategy);
ParkingGarage garage = new ParkingGarage(ticketService);
ParkingFloor floor1 = new ParkingFloor(1);
floor1.addSpot(new ParkingSpot(101, ParkingSpotType.COMPACT));
floor1.addSpot(new ParkingSpot(102, ParkingSpotType.REGULAR));
floor1.addSpot(new ParkingSpot(103, ParkingSpotType.LARGE));
floor1.addSpot(new ParkingSpot(104, ParkingSpotType.EV));
garage.addFloor(floor1);
Driver driver = new Driver("Arin", "Mauk", 29, "[email protected]");
Vehicle vehicle = new Vehicle(
"Honda",
"Civic",
2020,
VehicleType.COMPACT,
driver,
"ABC-123"
);
Ticket ticket = garage.createTicket(vehicle);
System.out.println("Ticket created: " + ticket.getTicketId());
System.out.println("Spot assigned: " + ticket.getSpot().getSpotNum());
Ticket closedTicket = garage.closeTicket(ticket.getTicketId());
System.out.println("Ticket closed.");
System.out.println("Fee: $" + closedTicket.getFee());
}
}
Explain design tradeoffs you considered. Check and explain whether your design adheres to SOLID principles. Explain how your design can handle changes in scale and whether it would be easy to extend with new functionalities. Identify areas for future improvement...
Design Tradeoffs:
Solid Principles:
Yes. PricingStrategy is small and focused: BigDecimal calculate(Ticket ticket);
Future improvement: