Requirements
The parking lot system will allow different vehicle in size entering and leaving the parking lot that is consisted of multiple floors and various types of spot. The system will handle spot assignment and fee calculation based on parking duration. The main functionalities are:
- Support multiple floors:
- each floor may contains different number and types of parking spot.
- cars can park at different floors
- Various types of spot
- motorcycle, car, handicapped, etc.
- Parking spots availability
- check spots availability and display to users in real-time
- Parking spots assignment
- assign spot based on vehicle type and spot availability.
- free the spot when a vehicle leaves
- Fee calculation
- record the duration when a spot is taken.
- calculate the final fee associated with this particular spot.
Analyze Relationships
Design Patterns
- Factory Pattern
- can instantiate different types of ParkingSpot and vehicle objects
public class VehicleFactory {
public static Vehicle createVehicle(VehicleType type, String licensePlate) {
switch(type) {
case CAR:
return new Car(licensePlate);
case MOTORCYCLE:
return new Motorcycle(licensePlate);
case VAN:
return new Van(licensePlate);
case ELECTRIC:
return new Electric(licensePlate);
case TRUCK:
return new Truck(licensePlate);
default:
throw new IllegalArgumentException("Unknown vehicle type: " + type);
}
}
}
public class ParkingSpotFactory{
public static createParkingSpot(ParkingSpotType type){
switch(type){
case LARGE:
return new Large();
}
}
}
- Singleton Pattern
- the parkingLot class can be instantiated using Singleton Pattern
public class ParkingLot{
private static ParkingLot instatnce;
private ParkingLot(){
}
public static synchronized ParkingLog getInstance(){
if(instance==null){
instance=new ParkingLot();
}
return instance;
}
}
- Strategy Pattern
- the fee schedule can be adjusted dynamically using strategy pattern
public interface FeeStrategy{
double calculateFee();
}
public class RegularFeeStrategy implements FeeStrategy{
@Override
double calculateFee(int baseFee, int feeFactor, Ticket ticket){
return baseFee+feeFactor*(ticket.exitTime-ticketEntryTime);
}
}
Define Class Members (write code)
- Vehicle
public class VehicleFactory {
public static Vehicle createVehicle(VehicleType type, String licensePlate) {
switch(type) {
case CAR:
return new Car(licensePlate);
case MOTORCYCLE:
return new Motorcycle(licensePlate);
case VAN:
return new Van(licensePlate);
case ELECTRIC:
return new Electric(licensePlate);
case TRUCK:
return new Truck(licensePlate);
default:
throw new IllegalArgumentException("Unknown vehicle type: " + type);
}
}
}
public abstract class Vehicle{
String licensePlate;
VehicleType type;
Ticket ticket;
public Vehicle(String _lp, VehicleType _type, Ticket ticket){
this.licensePlate=licensePlate;
this.type=_type;
this.ticket=ticket;
}
}
public class Car extends Vehicle{
public Car(String licensePlate, Ticket ticket){
super(licensePlate,VehicleType.CAR,ticket);
}
}
- ParkingSpot
public class ParkingSpotFactory{
public static createParkingSpot(ParkingSpotType type){
switch(type){
case LARGE:
return new Large();
}
}
}
public abstract class ParkingSpot{
int id;
ParkingSpotType type;
boolean isFree;
public ParkingSpot(ParkingSpotType type){
this.type=type;
isFree=true;
}
public boolean getIsFree(){
return isFree;
}
}
public class LargeSpot extends ParkingSpot{
super(ParkingSpotType.LARGE_SPOT);
}
- FeeStrategy
public interface FeeStrategy{
double calculateFee();
}
public class RegularFeeStrategy implements FeeStrategy{
@Override
double calculateFee(Ticket ticket){
int baseFee=10, int feeFactor=1;
return baseFee+feeFactor*(ticket.exitTime-ticketEntryTime);
}
}
- Ticket
public class Ticket{
int ticketId;
DateTime entryTime, exitTime;
ParkingTicketStatus status;
FeeStrategy feeStratety;
public Ticket(DateTime entryTime, FeeStrategy feeStrategy){
this.entryTime=entryTime;
this.status=ParkingTicketStatus.ACTIVE;
this.feeStrategy=feeStrategy;
}
}
- ParkingFloor
public class ParkingFloor{
int floorNum;
List<ParkingSpot> availableSpots;
Map<Vehicle, ParkingSpot> map_vehicle2Spot;
public ParkingFloor(int floorNum){
this.floorNum=floorNum;
availableSpots=new LinkedList<>();
map_vehicle2Spots=new HashMap<>();
}
public void freeSpot(Vehicle vehicle){
ParkingSpot spot=map_vehicle2Spots.get(vehicle);
availableSpots.add(spot);
map_vehicle2Spots.remove(vehicle);
}
public void assignSpot(Vechile vehicle){
map_vehicle2Spots.put(vehicle, availableSpots.get(availableSpots.size()-1));
availableSpots.remove(availableSpots.size()-1);
}
}
- ParkingLot
public class ParkingLot{
String name;
List<ParkingFloor> floors;
Map<Vehicle, ParkingFloor> map_vehicle2Floor;
private static ParkingLot instance;
private ParkingLot(String name){
this.name=name;
floors=new ArrayList<>();
map_vehicle2Floor=new HashMap<>();
}
public static synchronized getInstance(String name){
if(instance==null) instance=new ParkingLot(name);
return instance;
}
public void addFloor(){
ParkingFloor newFloor=new ParkingFloor(floors.size());
//skip adding spots
floors.add(newFloor)
}
public void enterParkingLot(Vehicle vehicle){
for(ParkingFloor floor:floors){
if(floor.availableSpots.size()>0){
DateTime curTime=new Date();
Ticket curTicket=new Ticket(curTime, new RegularFeeStrategy);
vehicle.ticket=curTicket;
floor.assignSpot(vehicle);
map_vehicle2Spot.put(vehicle,floor);
}
}
}
public void exitParkingLot(Vehicle vehicle){
Ticket ticket=vehicle.ticket;
ticket.feeStratege.claculateFee(ticket);
ParkingFloor floor=map_vehicle2Spot.get(vehicle);
floor.freeSpot(vehicle);
map_vehicle2Spot.remove(vehicle);
}
}
Adhere to SOLID Guidelines
- Single Responsibility Principle
- Each class in our design has a single responsibility. For example,
ParkingLotmanages the overall parking lot,ParkingSpotmanages an individual spot, andTickethandles parking tickets. This clear separation ensures that changes in one aspect of the system don’t affect other parts.
- Each class in our design has a single responsibility. For example,
- Open/Closed Principle
- The use of inheritance allows for extending functionality without modifying existing classes. For instance, new vehicle types (e.g.,
ElectricCar) can be added by creating new subclasses ofVehiclewithout altering the existingVehicleclass. Similarly, new fee calculation strategies can be added by implementing newFeeStrategyclasses.
- The use of inheritance allows for extending functionality without modifying existing classes. For instance, new vehicle types (e.g.,
- Liskov Substation Principle
- Subclasses like
Car,Bike, andTruckcan be used interchangeably with theVehiclesuperclass. The system’s behavior remains consistent regardless of the specific vehicle type, adhering to LSP.
- Subclasses like
- Interface Segregation Principle
- Use of abstract class (vehicle and parkingSpot) and interface makes sure that each class adheres to its specific role.
- Dependency Inversion Principle
- The use of strategies for fee calculation (e.g.,
FeeStrategyinterface) allows high-level classes likeTicketto depend on abstractions rather than concrete implementations. This makes the system more flexible and easier to modify or extend.
- The use of strategies for fee calculation (e.g.,
Consider Scalability and Flexibility
- Scalability
- horizontal scaling: can easily add more vehicle types, parking spot types
- vertical scaling: can easily add more floors nd spaces
- Flexibility
- horizontal and vertical scaling do not affect the system
- adding more features only needs to add more classes
Create/Explain your diagram(s)
Future improvements
- implement level availability
- allows users to pick their spots
- allows payment
- allows reservation