Designing a parking lot system is an excellent exercise for learning object-oriented programming (OOP) principles, modularity, and scalability. This article explains a step-by-step implementation of a parking lot system in Kotlin, focusing on clarity and logical structure. The content is written to cater to developers at all levels.
Problem Definition
We aim to build a parking lot system with the following requirements:
- Vehicle Types: Motorcycles, Cars, and Vans.
- Spot Types: Motorcycle spots, compact spots, and large spots.
- Parking Rules:
- Motorcycles can park in any spot.
- Cars can park in compact or larger spots.
- Vans need three consecutive large spots.
- System Features:
- Track available spots and their types.
- Determine if the parking lot is full or empty.
- Check if specific types of spots (e.g., motorcycle spots) are full.
High-Level Design
The solution follows a modular approach:
- Enums: Define vehicle and parking spot types.
- Interfaces: Abstract shared functionality for vehicles and spots.
- Classes: Concrete implementations for vehicles, spots, and parking lot management.
- Controller: High-level interface for interacting with the system.
Each section below breaks down the design in detail.
1. Defining Enums
Enums are ideal for defining fixed categories, like vehicle and spot types.
enum class VehicleType { MOTORCYCLE, CAR, VAN }
enum class SpotType { MOTORCYCLE, COMPACT, LARGE }
-
What This Does:
VehicleType
categorizes vehicles (Motorcycle, Car, Van).SpotType
categorizes parking spots (Motorcycle, Compact, Large).
-
Why It Matters:
- Enums make code more readable and maintainable. For example, instead of using arbitrary strings, you can use
VehicleType.CAR
.
- Enums make code more readable and maintainable. For example, instead of using arbitrary strings, you can use
2. Abstracting Vehicles
We use an interface to define the behavior of all vehicles. Specific vehicle types inherit this interface and add their unique properties.
interface Vehicle {
val type: VehicleType
val requiredSpots: Int
}
class Motorcycle : Vehicle {
override val type = VehicleType.MOTORCYCLE
override val requiredSpots = 1
}
class Car : Vehicle {
override val type = VehicleType.CAR
override val requiredSpots = 1
}
class Van : Vehicle {
override val type = VehicleType.VAN
override val requiredSpots = 3
}
-
What This Does:
Vehicle
defines shared properties:type
andrequiredSpots
.Motorcycle
,Car
, andVan
implement specific logic, like how many spots they need.
-
Why It Matters:
- Abstraction allows flexibility. If a new vehicle type is added, you only need to create a new class without changing the existing code.
3. Abstracting Parking Spots
Parking spots are represented by an interface and a concrete class.
interface ParkingSpot {
val id: Int
val type: SpotType
var isOccupied: Boolean
}
class GenericParkingSpot(
override val id: Int,
override val type: SpotType
) : ParkingSpot {
override var isOccupied = false
}
-
What This Does:
ParkingSpot
defines properties likeid
,type
, andisOccupied
.GenericParkingSpot
implements these properties.
-
Why It Matters:
- Decoupling spot behavior from its implementation makes the code flexible. For example, adding electric vehicle spots in the future requires only creating a new class.
4. Managing the Parking Lot
The ParkingLotManager
class handles the core functionality:
- Initializing parking spots.
- Allocating spots for vehicles.
- Removing vehicles.
- Providing status updates.
class ParkingLotManager(
motorcycleSpots: Int,
compactSpots: Int,
largeSpots: Int
) {
private val spots: MutableList<ParkingSpot> = mutableListOf()
init {
repeat(motorcycleSpots) { spots.add(GenericParkingSpot(spots.size + 1, SpotType.MOTORCYCLE)) }
repeat(compactSpots) { spots.add(GenericParkingSpot(spots.size + 1, SpotType.COMPACT)) }
repeat(largeSpots) { spots.add(GenericParkingSpot(spots.size + 1, SpotType.LARGE)) }
}
fun parkVehicle(vehicle: Vehicle): Boolean {
val availableSpots = spots.filter { !it.isOccupied && it.type.ordinal >= vehicle.type.ordinal }
if (availableSpots.size >= vehicle.requiredSpots) {
availableSpots.take(vehicle.requiredSpots).forEach { it.isOccupied = true }
println("${vehicle.type} parked successfully.")
return true
}
println("No space available for ${vehicle.type}.")
return false
}
fun removeVehicle(vehicle: Vehicle) {
val occupiedSpots = spots.filter { it.isOccupied && it.type.ordinal >= vehicle.type.ordinal }
if (occupiedSpots.size >= vehicle.requiredSpots) {
occupiedSpots.take(vehicle.requiredSpots).forEach { it.isOccupied = false }
println("${vehicle.type} removed successfully.")
} else {
println("No vehicle of type ${vehicle.type} found to remove.")
}
}
fun getRemainingSpots(): Int = spots.count { !it.isOccupied }
fun isFull(): Boolean = spots.none { !it.isOccupied }
fun isEmpty(): Boolean = spots.all { !it.isOccupied }
}
-
What This Does:
- Initializes spots based on configuration.
- Handles the logic for parking and removing vehicles.
- Tracks the parking lot's status.
-
Why It Matters:
- Centralized management makes it easier to add new features, like reserved spots or dynamic pricing.
5. Simplifying User Interaction
The ParkingLotController
abstracts parking lot management for the user. It combines common operations like parking, removing, and querying into a single interface.
class ParkingLotController(private val parkingLotManager: ParkingLotManager) {
fun park(vehicle: Vehicle) {
parkingLotManager.parkVehicle(vehicle)
}
fun remove(vehicle: Vehicle) {
parkingLotManager.removeVehicle(vehicle)
}
fun displayStatus() {
println("Remaining Spots: ${parkingLotManager.getRemainingSpots()}")
println("Is Full: ${parkingLotManager.isFull()}")
println("Is Empty: ${parkingLotManager.isEmpty()}")
}
}
-
What This Does:
- Simplifies interaction with the parking lot system.
- Focuses on common actions like parking and querying status.
-
Why It Matters:
- Abstracting complexity improves usability for developers using the system.
6. Putting It All Together
The main
function demonstrates how all components work together.
fun main() {
val parkingLotManager = ParkingLotManager(motorcycleSpots = 5, compactSpots = 10, largeSpots = 3)
val controller = ParkingLotController(parkingLotManager)
val motorcycle = Motorcycle()
val car = Car()
val van = Van()
controller.park(motorcycle)
controller.park(car)
controller.park(van)
controller.displayStatus()
controller.remove(car)
controller.displayStatus()
}
- What This Does:
- Creates a parking lot with specified spots.
- Parks and removes vehicles.
- Displays the parking lot's status after each operation.
Advantages of This Design
Modular and Maintainable:
- Each class/interface has a single responsibility.
- The code is easier to understand and maintain.
Scalable:
- Adding new vehicle or spot types is simple (e.g., adding EV spots or trucks).
Reusable:
- Interfaces (
Vehicle
,ParkingSpot
) ensure reusability and extensibility.
- Interfaces (
Adheres to OOP Principles:
- Encapsulation: Hides the implementation details of parking logic.
- Polymorphism: Handles different vehicle types using a common interface.
- Abstraction: Separates high-level logic from lower-level details.
Summary
This solution demonstrates a modular, extensible, and maintainable approach to designing a parking lot system in Kotlin. Key highlights include:
- Enums for categorization.
- Interfaces for abstraction.
- Classes for specific implementations.
- Centralized Management for parking logic.
- Simplified Interaction through a controller.
This design adheres to core OOP principles, such as encapsulation, abstraction, and polymorphism. It ensures that adding new features, such as electric vehicle spots or dynamic pricing, is straightforward.
Whether you’re a beginner learning Kotlin or an experienced developer designing complex systems, this approach provides a solid foundation for building scalable applications.
More details of problem go to LeetCode
HappyCoding
#Kotlin #Android #CodeChallenge