OOPS! Deep Dive 4: Polymorphism, Abstraction & Interfaces
Object-Oriented Programmingbeginnerdotnetjavaoop
Site Admin · 11/12/2026
Build a Parking Lot, part 4 of 5. Code and practice for the video, in Java and C#.
What you'll learn
- Make Vehicle abstract, with an abstract method every child class must fill in
- Override methods (@Override in Java; virtual and override in C#) and call the parent version with super / base
- Polymorphism: Ticket calls vehicle.fee(), and each object runs its own rule, chosen at run time
- Write an interface (Chargeable / IChargeable): one parent class, but as many interfaces as you need
Download
- parking-lot-dd04-java.zip: Java (lesson code, practice starter, solution)
- parking-lot-dd04-csharp.zip: C# (the same)
The code from the video
Java
Vehicle.java
// Deep Dive 4: Vehicle is abstract. Every vehicle must say its rate, and can change the fee rule.
public abstract class Vehicle {
private final String plate;
protected Vehicle(String plate) {
if (plate == null || plate.isBlank())
throw new IllegalArgumentException("plate is required");
this.plate = plate;
}
public String getPlate() { return plate; }
public abstract int hourlyRate(); // no body: each kind must fill it in
public int fee(int hours) { // the usual rule; a child may override it
return hours * hourlyRate();
}
public void describe() {
System.out.println(getClass().getSimpleName() + " " + plate);
}
}ParkingSpot.java
public class ParkingSpot {
private final int number;
private Vehicle parked; // null while free
public ParkingSpot(int number) {
if (number < 1)
throw new IllegalArgumentException("spot number must be 1 or more");
this.number = number;
}
public int getNumber() { return number; }
public boolean isFree() { return parked == null; }
public Ticket park(Vehicle v, int hour) {
if (!isFree())
throw new IllegalStateException("spot " + number + " is taken");
parked = v;
return new Ticket(v, this, hour); // this = this spot
}
public Vehicle leave() {
Vehicle v = parked; // null if the spot was already free: no crash
parked = null;
return v;
}
}Ticket.java
public class Ticket {
private static int nextId = 1; // one counter, shared by all tickets
private final int id;
private final Vehicle vehicle;
private final int spotNumber;
private final int entryHour;
public Ticket(Vehicle v, ParkingSpot s, int entryHour) {
if (entryHour < 0 || entryHour > 23)
throw new IllegalArgumentException("hour must be 0 to 23");
this.id = nextId++;
this.vehicle = v;
this.spotNumber = s.getNumber();
this.entryHour = entryHour;
}
public static int issued() { return nextId - 1; }
public int fee(int exitHour) {
int hours = Math.max(1, exitHour - entryHour);
return vehicle.fee(hours); // polymorphism: the vehicle's own rule runs
}
public void print() {
System.out.println("Ticket #" + id + " | " + vehicle.getPlate() + " | spot "
+ spotNumber + " | in at " + entryHour + ":00");
}
}Bike.java
public class Bike extends Vehicle {
public Bike(String plate) {
super(plate);
}
@Override
public int hourlyRate() { return 10; }
}Car.java
public class Car extends Vehicle {
public Car(String plate) {
super(plate);
}
@Override
public int hourlyRate() { return 20; }
}Chargeable.java
// An interface: a promise of what a class can do, not what it is.
public interface Chargeable {
int chargingFee(int kwh);
}ChargingPoint.java
public class ChargingPoint {
// Takes anything Chargeable: it doesn't care what the thing is.
public void charge(Chargeable c, int kwh) {
System.out.println("Charging " + kwh + " kWh = " + c.chargingFee(kwh));
}
}ElectricCar.java
public class ElectricCar extends Car implements Chargeable {
public ElectricCar(String plate) {
super(plate);
}
@Override
public int fee(int hours) {
return super.fee(hours) / 2; // half price: a different rule
}
@Override
public int chargingFee(int kwh) {
return kwh * 3;
}
}Main.java
public class Main {
public static void main(String[] args) {
Vehicle car = new Car("ABC-1234");
Vehicle bike = new Bike("XYZ-77");
ElectricCar ev = new ElectricCar("EVX-2025");
ParkingSpot spot1 = new ParkingSpot(1);
ParkingSpot spot2 = new ParkingSpot(2);
ParkingSpot spot3 = new ParkingSpot(3);
Ticket t1 = spot1.park(car, 9);
Ticket t2 = spot2.park(bike, 10);
Ticket t3 = spot3.park(ev, 11);
Vehicle[] all = { car, bike, ev };
for (Vehicle v : all)
v.describe(); // one call, three classes
System.out.println("Car fee at 12:00 = " + t1.fee(12));
System.out.println("Bike fee at 12:00 = " + t2.fee(12));
System.out.println("EV fee at 12:00 = " + t3.fee(12));
ChargingPoint charger = new ChargingPoint();
charger.charge(ev, 10);
}
}Output:
Car ABC-1234
Bike XYZ-77
ElectricCar EVX-2025
Car fee at 12:00 = 60
Bike fee at 12:00 = 20
EV fee at 12:00 = 10
Charging 10 kWh = 30C#
Vehicle.cs
// Deep Dive 4: Vehicle is abstract. Every vehicle must say its rate, and can change the fee rule.
public abstract class Vehicle
{
private readonly string plate;
protected Vehicle(string plate)
{
if (string.IsNullOrWhiteSpace(plate))
throw new ArgumentException("plate is required");
this.plate = plate;
}
public string Plate => plate;
public abstract int HourlyRate(); // no body: each kind must fill it in
public virtual int Fee(int hours) // the usual rule; virtual: a child may override it
{
return hours * HourlyRate();
}
public void Describe()
{
Console.WriteLine(GetType().Name + " " + plate);
}
}ParkingSpot.cs
public class ParkingSpot
{
private readonly int number;
private Vehicle parked; // null while free
public ParkingSpot(int number)
{
if (number < 1)
throw new ArgumentException("spot number must be 1 or more");
this.number = number;
}
public int Number => number;
public bool IsFree() => parked == null;
public Ticket Park(Vehicle v, int hour)
{
if (!IsFree())
throw new InvalidOperationException("spot " + number + " is taken");
parked = v;
return new Ticket(v, this, hour); // this = this spot
}
public Vehicle Leave()
{
Vehicle v = parked; // null if the spot was already free: no crash
parked = null;
return v;
}
}Ticket.cs
public class Ticket
{
private static int nextId = 1; // one counter, shared by all tickets
private readonly int id;
private readonly Vehicle vehicle;
private readonly int spotNumber;
private readonly int entryHour;
public Ticket(Vehicle v, ParkingSpot s, int entryHour)
{
if (entryHour < 0 || entryHour > 23)
throw new ArgumentException("hour must be 0 to 23");
this.id = nextId++;
this.vehicle = v;
this.spotNumber = s.Number;
this.entryHour = entryHour;
}
public static int Issued() => nextId - 1;
public int Fee(int exitHour)
{
int hours = Math.Max(1, exitHour - entryHour);
return vehicle.Fee(hours); // polymorphism: the vehicle's own rule runs
}
public void Print()
{
Console.WriteLine("Ticket #" + id + " | " + vehicle.Plate + " | spot "
+ spotNumber + " | in at " + entryHour + ":00");
}
}Bike.cs
public class Bike : Vehicle
{
public Bike(string plate) : base(plate)
{
}
public override int HourlyRate() => 10;
}Car.cs
public class Car : Vehicle
{
public Car(string plate) : base(plate)
{
}
public override int HourlyRate() => 20;
}ChargingPoint.cs
public class ChargingPoint
{
// Takes anything IChargeable: it doesn't care what the thing is.
public void Charge(IChargeable c, int kwh)
{
Console.WriteLine("Charging " + kwh + " kWh = " + c.ChargingFee(kwh));
}
}ElectricCar.cs
public class ElectricCar : Car, IChargeable
{
public ElectricCar(string plate) : base(plate)
{
}
public override int Fee(int hours)
{
return base.Fee(hours) / 2; // half price: a different rule
}
public int ChargingFee(int kwh) => kwh * 3;
}IChargeable.cs
// An interface: a promise of what a class can do, not what it is.
public interface IChargeable
{
int ChargingFee(int kwh);
}Program.cs
public class Program
{
public static void Main(string[] args)
{
Vehicle car = new Car("ABC-1234");
Vehicle bike = new Bike("XYZ-77");
ElectricCar ev = new ElectricCar("EVX-2025");
ParkingSpot spot1 = new ParkingSpot(1);
ParkingSpot spot2 = new ParkingSpot(2);
ParkingSpot spot3 = new ParkingSpot(3);
Ticket t1 = spot1.Park(car, 9);
Ticket t2 = spot2.Park(bike, 10);
Ticket t3 = spot3.Park(ev, 11);
Vehicle[] all = { car, bike, ev };
foreach (Vehicle v in all)
v.Describe(); // one call, three classes
Console.WriteLine("Car fee at 12:00 = " + t1.Fee(12));
Console.WriteLine("Bike fee at 12:00 = " + t2.Fee(12));
Console.WriteLine("EV fee at 12:00 = " + t3.Fee(12));
ChargingPoint charger = new ChargingPoint();
charger.Charge(ev, 10);
}
}Output:
Car ABC-1234
Bike XYZ-77
ElectricCar EVX-2025
Car fee at 12:00 = 60
Bike fee at 12:00 = 20
EV fee at 12:00 = 10
Charging 10 kWh = 30Practice: Bring the Truck into the new design
Practice (Deep Dive 4): bring the Truck from Deep Dive 3 into the new design.
1. Write a class Truck that extends Vehicle, with its own field: int axles.
Constructor: Truck(String plate, int axles). Call super(plate) first,
and reject fewer than 2 axles.
2. Vehicle now asks every kind for its rate: override hourlyRate() and return 40.
3. Trucks pay for at least 2 hours: override fee(int hours).
Bonus: reuse the parent's rule with super.fee(...) instead of writing hours * rate again.
Ticket must not change. Then uncomment the truck lines in Main.java.
Expected: Truck fee at 12:00 = 80 (in at 11: 1 hour, charged as 2)(Names above are the Java ones; the C# starter below uses the C# names in its comments.)
Start from the lesson code above. Only these files change; the rest are the same as the lesson.
Starter (Java)
Truck.java
// Practice (Deep Dive 4): bring the Truck from Deep Dive 3 into the new design.
//
// 1. Write a class Truck that extends Vehicle, with its own field: int axles.
// Constructor: Truck(String plate, int axles). Call super(plate) first,
// and reject fewer than 2 axles.
// 2. Vehicle now asks every kind for its rate: override hourlyRate() and return 40.
// 3. Trucks pay for at least 2 hours: override fee(int hours).
// Bonus: reuse the parent's rule with super.fee(...) instead of writing hours * rate again.
//
// Ticket must not change. Then uncomment the truck lines in Main.java.
// Expected: Truck fee at 12:00 = 80 (in at 11: 1 hour, charged as 2)Main.java
public class Main {
public static void main(String[] args) {
Vehicle car = new Car("ABC-1234");
Vehicle bike = new Bike("XYZ-77");
ElectricCar ev = new ElectricCar("EVX-2025");
ParkingSpot spot1 = new ParkingSpot(1);
ParkingSpot spot2 = new ParkingSpot(2);
ParkingSpot spot3 = new ParkingSpot(3);
Ticket t1 = spot1.park(car, 9);
Ticket t2 = spot2.park(bike, 10);
Ticket t3 = spot3.park(ev, 11);
Vehicle[] all = { car, bike, ev };
for (Vehicle v : all)
v.describe(); // one call, three classes
System.out.println("Car fee at 12:00 = " + t1.fee(12));
System.out.println("Bike fee at 12:00 = " + t2.fee(12));
System.out.println("EV fee at 12:00 = " + t3.fee(12));
ChargingPoint charger = new ChargingPoint();
charger.charge(ev, 10);
// When Truck is done, uncomment these lines:
// ParkingSpot spot4 = new ParkingSpot(4);
// Truck truck = new Truck("TRK-4040", 3);
// Ticket t4 = spot4.park(truck, 11);
// truck.describe();
// System.out.println("Truck fee at 12:00 = " + t4.fee(12));
}
}Starter (C#)
Truck.cs
// Practice (Deep Dive 4): bring the Truck from Deep Dive 3 into the new design.
//
// 1. Write a class Truck : Vehicle, with its own field: int axles.
// Constructor: Truck(string plate, int axles) : base(plate),
// and reject fewer than 2 axles.
// 2. Vehicle now asks every kind for its rate: override HourlyRate() and return 40.
// 3. Trucks pay for at least 2 hours: override Fee(int hours).
// Bonus: reuse the parent's rule with base.Fee(...) instead of writing hours * rate again.
//
// Ticket must not change. Then uncomment the truck lines in Program.cs.
// Expected: Truck fee at 12:00 = 80 (in at 11: 1 hour, charged as 2)Program.cs
public class Program
{
public static void Main(string[] args)
{
Vehicle car = new Car("ABC-1234");
Vehicle bike = new Bike("XYZ-77");
ElectricCar ev = new ElectricCar("EVX-2025");
ParkingSpot spot1 = new ParkingSpot(1);
ParkingSpot spot2 = new ParkingSpot(2);
ParkingSpot spot3 = new ParkingSpot(3);
Ticket t1 = spot1.Park(car, 9);
Ticket t2 = spot2.Park(bike, 10);
Ticket t3 = spot3.Park(ev, 11);
Vehicle[] all = { car, bike, ev };
foreach (Vehicle v in all)
v.Describe(); // one call, three classes
Console.WriteLine("Car fee at 12:00 = " + t1.Fee(12));
Console.WriteLine("Bike fee at 12:00 = " + t2.Fee(12));
Console.WriteLine("EV fee at 12:00 = " + t3.Fee(12));
ChargingPoint charger = new ChargingPoint();
charger.Charge(ev, 10);
// When Truck is done, uncomment these lines:
// ParkingSpot spot4 = new ParkingSpot(4);
// Truck truck = new Truck("TRK-4040", 3);
// Ticket t4 = spot4.Park(truck, 11);
// truck.Describe();
// Console.WriteLine("Truck fee at 12:00 = " + t4.Fee(12));
}
}Solution
Try it yourself first!
Show the Java solution
Truck.java
public class Truck extends Vehicle {
private final int axles;
public Truck(String plate, int axles) {
super(plate);
if (axles < 2)
throw new IllegalArgumentException("a truck has at least 2 axles");
this.axles = axles;
}
public int getAxles() { return axles; }
@Override
public int hourlyRate() { return 40; }
@Override
public int fee(int hours) {
return super.fee(Math.max(2, hours)); // at least 2 hours
}
}Main.java
public class Main {
public static void main(String[] args) {
Vehicle car = new Car("ABC-1234");
Vehicle bike = new Bike("XYZ-77");
ElectricCar ev = new ElectricCar("EVX-2025");
ParkingSpot spot1 = new ParkingSpot(1);
ParkingSpot spot2 = new ParkingSpot(2);
ParkingSpot spot3 = new ParkingSpot(3);
Ticket t1 = spot1.park(car, 9);
Ticket t2 = spot2.park(bike, 10);
Ticket t3 = spot3.park(ev, 11);
Vehicle[] all = { car, bike, ev };
for (Vehicle v : all)
v.describe(); // one call, three classes
System.out.println("Car fee at 12:00 = " + t1.fee(12));
System.out.println("Bike fee at 12:00 = " + t2.fee(12));
System.out.println("EV fee at 12:00 = " + t3.fee(12));
ChargingPoint charger = new ChargingPoint();
charger.charge(ev, 10);
ParkingSpot spot4 = new ParkingSpot(4);
Truck truck = new Truck("TRK-4040", 3);
Ticket t4 = spot4.park(truck, 11);
truck.describe();
System.out.println("Truck fee at 12:00 = " + t4.fee(12));
}
}Output:
Car ABC-1234
Bike XYZ-77
ElectricCar EVX-2025
Car fee at 12:00 = 60
Bike fee at 12:00 = 20
EV fee at 12:00 = 10
Charging 10 kWh = 30
Truck TRK-4040
Truck fee at 12:00 = 80Show the C# solution
Truck.cs
public class Truck : Vehicle
{
private readonly int axles;
public Truck(string plate, int axles) : base(plate)
{
if (axles < 2)
throw new ArgumentException("a truck has at least 2 axles");
this.axles = axles;
}
public int Axles => axles;
public override int HourlyRate() => 40;
public override int Fee(int hours)
{
return base.Fee(Math.Max(2, hours)); // at least 2 hours
}
}Program.cs
public class Program
{
public static void Main(string[] args)
{
Vehicle car = new Car("ABC-1234");
Vehicle bike = new Bike("XYZ-77");
ElectricCar ev = new ElectricCar("EVX-2025");
ParkingSpot spot1 = new ParkingSpot(1);
ParkingSpot spot2 = new ParkingSpot(2);
ParkingSpot spot3 = new ParkingSpot(3);
Ticket t1 = spot1.Park(car, 9);
Ticket t2 = spot2.Park(bike, 10);
Ticket t3 = spot3.Park(ev, 11);
Vehicle[] all = { car, bike, ev };
foreach (Vehicle v in all)
v.Describe(); // one call, three classes
Console.WriteLine("Car fee at 12:00 = " + t1.Fee(12));
Console.WriteLine("Bike fee at 12:00 = " + t2.Fee(12));
Console.WriteLine("EV fee at 12:00 = " + t3.Fee(12));
ChargingPoint charger = new ChargingPoint();
charger.Charge(ev, 10);
ParkingSpot spot4 = new ParkingSpot(4);
Truck truck = new Truck("TRK-4040", 3);
Ticket t4 = spot4.Park(truck, 11);
truck.Describe();
Console.WriteLine("Truck fee at 12:00 = " + t4.Fee(12));
}
}Output:
Car ABC-1234
Bike XYZ-77
ElectricCar EVX-2025
Car fee at 12:00 = 60
Bike fee at 12:00 = 20
EV fee at 12:00 = 10
Charging 10 kWh = 30
Truck TRK-4040
Truck fee at 12:00 = 80How to run
- Java (17 or newer): in the folder,
javac -d out *.javathenjava -cp out Main - C# (.NET SDK 9 or newer): in the folder,
dotnet run
Previous: Deep Dive 3: Inheritance (super / base) · Next: Deep Dive 5: Composition & the Full System
Comments
Loading comments…