Lab: Encapsulation#
Objective#
In this lab, we delve into the principle of encapsulation by refactoring a Car
class. We will control access to the Speed
and TopSpeed
attributes through instance methods, ensuring that the state of our car objects remains valid and realistic.
Provided Code#
Carefully review the provided code. Notice the public fields Speed
and TopSpeed
that allow any part of the code to modify these values, potentially leading to inconsistent object states.
class Car
{
public string Name;
public int Speed;
public int TopSpeed;
public void Accelerate()
{
if (Speed < TopSpeed)
Speed++;
}
public void Brake()
{
if (Speed > 0)
Speed--;
}
public void Print()
{
Console.WriteLine($"{Name}: {Speed} ({TopSpeed})");
}
}
While the instance methods Accelerate
and Brake
indeed respects the top speed and the requirement that speed cannot be negative we can bypass this by changing the Speed
field directly.
Car beetle = new Car();
beetle.Name = "Beetle";
beetle.TopSpeed = 120;
beetle.Speed = 30000; // 🤔 Wait a minute...
beetle.Speed = -400; // Eh, is reversing allowed?
Instructions#
Step 1: Make the Fields Private#
Convert all the fields into private fields in order to encapsulate them.
class Car
{
private string name;
private int speed;
private int topSpeed;
// ...
}
Note
Remember that members are private
by default so we can actually emit the keyword private
alltogether should we so wish.
Note
Since private
fields are conventionally named using camelCase rather than pascalCase, remember to change the name of the fields.
Step 2: Implement Instance Methods for Modification#
Let’s now write methods to accellerate and brake in a controlled manner.
class Car
{
private string name;
private int speed;
private int topSpeed;
public void Accelerate() {
if (speed < topSpeed)
speed++;
}
public void Brake() {
if (speed > 0)
speed--;
}
public void Print()
{
Console.WriteLine($"{name}: {speed} ({topSpeed})");
}
}
But, how are we now supposed to set the topSpeed
and name
? 🤷
Step 3: Set initial state using a Constructor#
Let’s add a constructor that takes a parameter of type string
called name
and an int
called topSpeed
.
class Car
{
private string name;
private int speed;
private int topSpeed;
public Car (string name, int topSpeed)
{
this.name = name;
this.topSpeed = topSpeed;
}
public void Accelerate() {
if (speed < topSpeed)
speed++;
}
public void Brake() {
if (speed > 0)
speed--;
}
public void Print()
{
Console.WriteLine($"{name}: {speed} ({topSpeed})");
}
}
🤔 Reflection
How is this code more encapsulated than the code that we started with?
Can an object of type Car
now end up in an inconsistent state where Speed
exceeds topSpeed
or is below 0?
Step 4: Test the Code#
Instantiate two Car
objects with different top speed and check whether their Accelerate
and Brake
methods seem to work.
Car roadster = new Car("Roadster", 3);
Car cybertruck = new Car("Cybertruck", 1);
Car[] cars = new Car[] { roadster, cybertruck };
// Accellerate
foreach (Car car in cars)
{
for (int i=0; i<5; i++)
car.Accelerate();
car.Print();
for (int i=0; i<5; i++)
car.Brake();
car.Print();
}
Roadster: 3 (3)
Roadster: 0 (3)
Cybertruck: 1 (1)
Cybertruck: 0 (1)
Challenge#
Add a new instance method to the Car
class with the signature void Match(Car other)
. The method should set the speed of this car to the speed of the other.
Car roadster = new Car("Roadster", 3);
Car cybertruck = new Car("Cybertruck", 1);
roadster.Accelerate();
roadster.Accelerate();
roadster.Accelerate();
cybertruck.MatchSpeed(roadster);
cybertruck.Print(); // Oh no 😬
Cybertruck: 3 (1)
🤔 Reflection
By creating the Match
method, we are allowing one instance of Car
to interact with the private state of another instance. What does this teach us about how private
works? How might this be useful in practice?
Through this lab, we have put the concept of encapsulation into practice by limiting access to the internal state of our Car
objects, thereby enhancing the robustness and reliability of our code.
In effect, we have encapsulated the implementation details, or “hidden our private parts” 😳.
But we’ve also reflected over how the nuances caused by how private
members are private to the type in which they are defined, not to objects of the class.
Good job! 👊