I have tried to design the parking lot problem. Here is the problem statement.
Design a parking lot system where Admin can look up all the available spots for parking, He should be able to choose both randomly as well as orderly Using the register number and the type of the vehicle he should be able to issue the parking ticket During the checkout, the cost should be calculated as per length of the time the vehicle stayed inside the parking lot Each slots have a defined capacity and each vehicle has defined space required.
Admin.java
public class Admin {
private final ParkingLot parkingLot = new ParkingLot("My parking space");
private final ParkingTicketPrinter parkingTicketPrinter = new ParkingTicketPrinter();
private final List<Ticket> liveTickets = new ArrayList<>();
public List<Slot> getAvailableSlots() {
final List<Slot> result = new ArrayList<>();
for (Floor floor : getFloors()) {
for (Slot slot : floor.getSlots()) {
if (!slot.isOccupied())
result.add(slot);
}
}
return result;
}
private List<Floor> getFloors() {
return parkingLot.getFloors();
}
private Slot pickRandomSlot() {
final List<Slot> availableSlots = getAvailableSlots();
final int randomNum = ThreadLocalRandom.current().nextInt(1, availableSlots.size());
return availableSlots.get(randomNum);
}
private void orderFloors() {
List<Floor> floors = parkingLot.getFloors();
Collections.sort(floors);
}
public void issueParkingTicket(boolean randomSlotOk, String registerNumber, VehicleTypes type) {
final Slot slot = randomSlotOk ? pickRandomSlot() : getNearestSlot();
Ticket ticket = new Ticket(slot,registerNumber, type);
liveTickets.add(ticket);
parkingTicketPrinter.printTicket(ticket);
slot.setOccupied(true);
}
public void collectAmount(Ticket ticket) {
liveTickets.remove(ticket);
double cost = ticket.getCost();
System.out.println("Amount to be collected "+ cost);
}
private Slot getNearestSlot() {
orderFloors();
final List<Slot> availableSlots = getAvailableSlots();
for (Slot slot : availableSlots) {
if (!slot.isOccupied())
return slot;
}
return null;
}
public List<String> getLiveTicketIds() {
List<String> result = new ArrayList<>();
for (Ticket liveTicket : liveTickets) result.add(liveTicket.getTicketNumber());
return result;
}
public void addFloor(long floorNumber) {
parkingLot.addFloor(new Floor(floorNumber));
}
}
Ticket related classes
public class Ticket {
private String ticketNumber;
private Slot slot;
private Vehicle vehicle;
private CostCalculator costCalculator;
private TicketHelper ticketHelper = new TicketHelper();
private long inTime = System.currentTimeMillis();
private static AtomicLong atomicLong = new AtomicLong();
public Ticket(Slot slot, String registerNumber, VehicleTypes types) {
this.ticketNumber = String.valueOf(atomicLong.get());
this.slot = slot;
this.vehicle = ticketHelper.createVehicle(registerNumber,types);
this.costCalculator = ticketHelper.getCostCalculator(types);
}
public String getTicketNumber() {
return ticketNumber;
}
public String getPrintableString() {
return slot.getInfo() + "\n" + vehicle.getInfo();
}
public double getCost() {
return costCalculator.getCost(inTime);
}
}
public class TicketHelper {
public Vehicle createVehicle(String registerNumber, VehicleTypes type) {
if (type == VehicleTypes.Car) {
return new Car(registerNumber);
} else if (type == VehicleTypes.Bus) {
return new Bus(registerNumber);
}
return null;
}
public CostCalculator getCostCalculator(VehicleTypes types) {
if (types == VehicleTypes.Car) {
return new CarCostCalculator();
} else if (types == VehicleTypes.Bus) {
return new BusCostCalculator();
}
return null;
}
}
Vehicle classes
public class Bus extends Vehicle {
private int neededSpace = 3;
public Bus(String registerNumber) {
super(registerNumber);
}
public int getNeededSpace() {
return neededSpace;
}
public String getInfo() {
return "Bus" + "\n" + " registernumber " + getRegisterNumber();
}
}
public class Car extends Vehicle {
private int neededSpace = 2;
public Car(String registerNumber) {
super(registerNumber);
}
public int getNeededSpace() {
return neededSpace;
}
public String getInfo() {
return "Car " + "\n" + " registernumber " + getRegisterNumber();
}
}
public abstract class Vehicle {
private String registerNumber;
public Vehicle(String registerNumber) {
this.registerNumber = registerNumber;
}
public String getRegisterNumber() {
return registerNumber;
}
abstract public String getInfo();
}
public enum VehicleTypes {
Car, Bus, TwoWheeler
}
Cost logic
public interface CostCalculator {
double getCost(long inTime);
}
public class CarCostCalculator implements CostCalculator {
@Override
public double getCost(long inTime) {
return (System.currentTimeMillis() - inTime) * 0.6;
}
}
public class BusCostCalculator implements CostCalculator {
@Override
public double getCost(long inTime) {
return (System.currentTimeMillis() - inTime) * 0.4;
}
}
Inventory logic
public class Floor implements Comparable<Floor>{
private long floorNumber;
private List<Slot> slots = new ArrayList<>();
private long occupiedCount;
public List<Slot> getSlots() {
return slots;
}
public Floor(long floorNumber) {
this.floorNumber = floorNumber;
}
public long getOccupiedCount() {
return occupiedCount;
}
public long getFreeSlotCount() {
return slots.size() - occupiedCount;
}
public boolean isAvailable() {
return getFreeSlotCount() > 0;
}
public long getFloorNumber() {
return floorNumber;
}
public void setSlots(List<Slot> slots) {
this.slots = slots;
}
public void addSlot(Slot slot) {
slots.add(slot);
}
public void removeSlot(Slot slot) {
slots.remove(slot);
}
@Override public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Floor floor = (Floor) o;
return floorNumber == floor.floorNumber;
}
@Override public int hashCode() {
return (int) (floorNumber ^ (floorNumber >>> 32));
}
@Override public int compareTo(Floor otherFloor) {
if (otherFloor.getFloorNumber() > getFloorNumber())
return 1;
else if (otherFloor.getFloorNumber() < getFloorNumber())
return -1;
return 0;
}
}
public class ParkingLot {
private String parkingLotName;
private List<Floor> floors = new ArrayList<>();
public ParkingLot(String parkingLotName) {
this.parkingLotName = parkingLotName;
}
public String getParkingLotName() {
return parkingLotName;
}
public List<Floor> getFloors() {
return floors;
}
public void addFloor(Floor floor) {
floors.add(floor);
}
public void deleteFloor(Floor floor) {
floors.remove(floor);
}
}
public class Slot {
private long slotNumber;
private boolean isOccupied;
private int capacity;
public Slot(long slotNumber, int capacity) {
this.slotNumber = slotNumber;
this.capacity = capacity;
}
public void setOccupied(boolean occupied) {
isOccupied = occupied;
}
public boolean isOccupied() {
return isOccupied;
}
public long getSlotNumber() {
return slotNumber;
}
public int getCapacity() {
return capacity;
}
public String getInfo() {
return "Slot{" +
"slotNumber=" + slotNumber +
", capacity=" + capacity +
'}';
}
}
Printing logic
public class ParkingTicketPrinter {
private PrintWriter printWriter ;
public ParkingTicketPrinter() {
try {
this.printWriter = new PrintWriter("");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public void printTicket(Ticket ticket) {
printWriter.write(ticket.getPrintableString());
}
}
This is the best I can come up with for now. Please point out if there is any major design flaws or the better way to do the things I just did.