OOPS! Deep Dive 2: Encapsulation, this & static
Object-Oriented Programmingbeginnerdotnetjavaoop
Site Admin · 11/12/2026
Build a Parking Lot, part 2 of 5. Code and practice for the video, in Java and C#.
What you'll learn
- Make fields private and check every value in the constructor
- Read data through getters (Java) or read-only properties (C#), with no setters
- Let methods guard their own rules: park() refuses a taken spot, leave() never crashes
- The three uses of this, and static: one ticket counter shared by the whole class
Download
- parking-lot-dd02-java.zip: Java (lesson code, practice starter, solution)
- parking-lot-dd02-csharp.zip: C# (the same)
The code from the video
Java
Vehicle.java
// Deep Dive 2: private fields, checked in the constructor, read through getters.
public class Vehicle {
private final String plate; // set once, in the constructor
private final String type;
public Vehicle(String plate, String type) {
if (plate == null || plate.isBlank())
throw new IllegalArgumentException("plate is required");
this.plate = plate;
this.type = type;
}
public Vehicle(String plate) {
this(plate, "CAR");
}
public String getPlate() { return plate; } // a getter, and no setter
public String getType() { return type; }
public void describe() {
System.out.println(type + " " + 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 static final int RATE = 20; // per started hour
private final int id;
private final String plate;
private final int spotNumber;
private final int entryHour;
public Ticket(Vehicle v, ParkingSpot s, int entryHour) {
this.id = nextId++;
this.plate = v.getPlate();
this.spotNumber = s.getNumber();
this.entryHour = entryHour;
}
public static int issued() { return nextId - 1; }
public int fee(int exitHour) {
return Math.max(1, exitHour - entryHour) * RATE;
}
public void print() {
System.out.println("Ticket #" + id + " | " + plate + " | spot "
+ spotNumber + " | in at " + entryHour + ":00");
}
}Main.java
public class Main {
public static void main(String[] args) {
Vehicle car = new Vehicle("ABC-1234", "CAR");
Vehicle bike = new Vehicle("XYZ-77", "BIKE");
ParkingSpot spot1 = new ParkingSpot(1);
ParkingSpot spot2 = new ParkingSpot(2);
Ticket t1 = spot1.park(car, 9);
Ticket t2 = spot2.park(bike, 10);
t1.print();
t2.print();
System.out.println("Tickets issued: " + Ticket.issued());
System.out.println("Fee for ticket 1 at 12:00 = " + t1.fee(12));
}
}Output:
Ticket #1 | ABC-1234 | spot 1 | in at 9:00
Ticket #2 | XYZ-77 | spot 2 | in at 10:00
Tickets issued: 2
Fee for ticket 1 at 12:00 = 60C#
Vehicle.cs
// Deep Dive 2: private fields, checked in the constructor, read through getters.
public class Vehicle
{
private readonly string plate; // set once, in the constructor
private readonly string type;
public Vehicle(string plate, string type)
{
if (string.IsNullOrWhiteSpace(plate))
throw new ArgumentException("plate is required");
this.plate = plate;
this.type = type;
}
public Vehicle(string plate) : this(plate, "CAR")
{
}
public string Plate => plate; // a getter, and no setter
public string Type => type;
public void Describe()
{
Console.WriteLine(type + " " + 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 const int Rate = 20; // per started hour (const is always static)
private readonly int id;
private readonly string plate;
private readonly int spotNumber;
private readonly int entryHour;
public Ticket(Vehicle v, ParkingSpot s, int entryHour)
{
this.id = nextId++;
this.plate = v.Plate;
this.spotNumber = s.Number;
this.entryHour = entryHour;
}
public static int Issued() => nextId - 1;
public int Fee(int exitHour) => Math.Max(1, exitHour - entryHour) * Rate;
public void Print()
{
Console.WriteLine("Ticket #" + id + " | " + plate + " | spot "
+ spotNumber + " | in at " + entryHour + ":00");
}
}Program.cs
public class Program
{
public static void Main(string[] args)
{
Vehicle car = new Vehicle("ABC-1234", "CAR");
Vehicle bike = new Vehicle("XYZ-77", "BIKE");
ParkingSpot spot1 = new ParkingSpot(1);
ParkingSpot spot2 = new ParkingSpot(2);
Ticket t1 = spot1.Park(car, 9);
Ticket t2 = spot2.Park(bike, 10);
t1.Print();
t2.Print();
Console.WriteLine("Tickets issued: " + Ticket.Issued());
Console.WriteLine("Fee for ticket 1 at 12:00 = " + t1.Fee(12));
}
}Output:
Ticket #1 | ABC-1234 | spot 1 | in at 9:00
Ticket #2 | XYZ-77 | spot 2 | in at 10:00
Tickets issued: 2
Fee for ticket 1 at 12:00 = 60Practice: Different rates for cars and bikes
Practice (Deep Dive 2): bikes pay less than cars.
1. Add a private final String type, copied from the vehicle in the constructor.
2. Replace RATE with two static constants: CAR_RATE = 20 and BIKE_RATE = 10.
3. Make fee() pick the rate that matches the type.
Bonus: in the constructor, reject an entryHour outside 0 to 23
(throw new IllegalArgumentException("hour must be 0 to 23")).(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)
Ticket.java
// Practice (Deep Dive 2): bikes pay less than cars.
//
// 1. Add a private final String type, copied from the vehicle in the constructor.
// 2. Replace RATE with two static constants: CAR_RATE = 20 and BIKE_RATE = 10.
// 3. Make fee() pick the rate that matches the type.
// Bonus: in the constructor, reject an entryHour outside 0 to 23
// (throw new IllegalArgumentException("hour must be 0 to 23")).
public class Ticket {
private static int nextId = 1; // one counter, shared by all tickets
private static final int RATE = 20; // per started hour
private final int id;
private final String plate;
private final int spotNumber;
private final int entryHour;
public Ticket(Vehicle v, ParkingSpot s, int entryHour) {
this.id = nextId++;
this.plate = v.getPlate();
this.spotNumber = s.getNumber();
this.entryHour = entryHour;
}
public static int issued() { return nextId - 1; }
public int fee(int exitHour) {
return Math.max(1, exitHour - entryHour) * RATE;
}
public void print() {
System.out.println("Ticket #" + id + " | " + plate + " | spot "
+ spotNumber + " | in at " + entryHour + ":00");
}
}Main.java
public class Main {
public static void main(String[] args) {
Vehicle car = new Vehicle("ABC-1234", "CAR");
Vehicle bike = new Vehicle("XYZ-77", "BIKE");
ParkingSpot spot1 = new ParkingSpot(1);
ParkingSpot spot2 = new ParkingSpot(2);
Ticket t1 = spot1.park(car, 9);
Ticket t2 = spot2.park(bike, 10);
t1.print();
t2.print();
System.out.println("Tickets issued: " + Ticket.issued());
System.out.println("Car fee at 12:00 = " + t1.fee(12));
System.out.println("Bike fee at 12:00 = " + t2.fee(12));
}
}Starter (C#)
Ticket.cs
// Practice (Deep Dive 2): bikes pay less than cars.
//
// 1. Add a private readonly string type, copied from the vehicle in the constructor.
// 2. Replace Rate with two constants: CarRate = 20 and BikeRate = 10.
// 3. Make Fee() pick the rate that matches the type.
// Bonus: in the constructor, reject an entryHour outside 0 to 23
// (throw new ArgumentException("hour must be 0 to 23")).
public class Ticket
{
private static int nextId = 1; // one counter, shared by all tickets
private const int Rate = 20; // per started hour (const is always static)
private readonly int id;
private readonly string plate;
private readonly int spotNumber;
private readonly int entryHour;
public Ticket(Vehicle v, ParkingSpot s, int entryHour)
{
this.id = nextId++;
this.plate = v.Plate;
this.spotNumber = s.Number;
this.entryHour = entryHour;
}
public static int Issued() => nextId - 1;
public int Fee(int exitHour) => Math.Max(1, exitHour - entryHour) * Rate;
public void Print()
{
Console.WriteLine("Ticket #" + id + " | " + plate + " | spot "
+ spotNumber + " | in at " + entryHour + ":00");
}
}Program.cs
public class Program
{
public static void Main(string[] args)
{
Vehicle car = new Vehicle("ABC-1234", "CAR");
Vehicle bike = new Vehicle("XYZ-77", "BIKE");
ParkingSpot spot1 = new ParkingSpot(1);
ParkingSpot spot2 = new ParkingSpot(2);
Ticket t1 = spot1.Park(car, 9);
Ticket t2 = spot2.Park(bike, 10);
t1.Print();
t2.Print();
Console.WriteLine("Tickets issued: " + Ticket.Issued());
Console.WriteLine("Car fee at 12:00 = " + t1.Fee(12));
Console.WriteLine("Bike fee at 12:00 = " + t2.Fee(12));
}
}Solution
Try it yourself first!
Show the Java solution
Ticket.java
public class Ticket {
private static int nextId = 1;
private static final int CAR_RATE = 20;
private static final int BIKE_RATE = 10;
private final int id;
private final String plate;
private final String type;
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.plate = v.getPlate();
this.type = v.getType();
this.spotNumber = s.getNumber();
this.entryHour = entryHour;
}
public static int issued() { return nextId - 1; }
public int fee(int exitHour) {
int rate = type.equals("BIKE") ? BIKE_RATE : CAR_RATE;
return Math.max(1, exitHour - entryHour) * rate;
}
public void print() {
System.out.println("Ticket #" + id + " | " + plate + " | spot "
+ spotNumber + " | in at " + entryHour + ":00");
}
}Main.java
public class Main {
public static void main(String[] args) {
Vehicle car = new Vehicle("ABC-1234", "CAR");
Vehicle bike = new Vehicle("XYZ-77", "BIKE");
ParkingSpot spot1 = new ParkingSpot(1);
ParkingSpot spot2 = new ParkingSpot(2);
Ticket t1 = spot1.park(car, 9);
Ticket t2 = spot2.park(bike, 10);
t1.print();
t2.print();
System.out.println("Tickets issued: " + Ticket.issued());
System.out.println("Car fee at 12:00 = " + t1.fee(12));
System.out.println("Bike fee at 12:00 = " + t2.fee(12));
}
}Output:
Ticket #1 | ABC-1234 | spot 1 | in at 9:00
Ticket #2 | XYZ-77 | spot 2 | in at 10:00
Tickets issued: 2
Car fee at 12:00 = 60
Bike fee at 12:00 = 20Show the C# solution
Ticket.cs
public class Ticket
{
private static int nextId = 1;
private const int CarRate = 20;
private const int BikeRate = 10;
private readonly int id;
private readonly string plate;
private readonly string type;
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.plate = v.Plate;
this.type = v.Type;
this.spotNumber = s.Number;
this.entryHour = entryHour;
}
public static int Issued() => nextId - 1;
public int Fee(int exitHour)
{
int rate = type == "BIKE" ? BikeRate : CarRate;
return Math.Max(1, exitHour - entryHour) * rate;
}
public void Print()
{
Console.WriteLine("Ticket #" + id + " | " + plate + " | spot "
+ spotNumber + " | in at " + entryHour + ":00");
}
}Program.cs
public class Program
{
public static void Main(string[] args)
{
Vehicle car = new Vehicle("ABC-1234", "CAR");
Vehicle bike = new Vehicle("XYZ-77", "BIKE");
ParkingSpot spot1 = new ParkingSpot(1);
ParkingSpot spot2 = new ParkingSpot(2);
Ticket t1 = spot1.Park(car, 9);
Ticket t2 = spot2.Park(bike, 10);
t1.Print();
t2.Print();
Console.WriteLine("Tickets issued: " + Ticket.Issued());
Console.WriteLine("Car fee at 12:00 = " + t1.Fee(12));
Console.WriteLine("Bike fee at 12:00 = " + t2.Fee(12));
}
}Output:
Ticket #1 | ABC-1234 | spot 1 | in at 9:00
Ticket #2 | XYZ-77 | spot 2 | in at 10:00
Tickets issued: 2
Car fee at 12:00 = 60
Bike fee at 12:00 = 20How 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 1: Classes, Objects & Constructors · Next: Deep Dive 3: Inheritance (super / base)
Comments
Loading comments…