How To Design An Abstraction
How do we design a useful abstraction? Our notion of a good abstraction evolves as we encounter more specific types.
OK, let’s investigate with an example:
Say we have a system modelling cars. Initially, the program only caters to petrol cars—we create a PetrolCar
class. PetrolCar
must keep track of how much petrol remains in the tank. To that end, we could create a property called LitresOfPetrolRemaining
on PetrolCar
.
PetrolCar petrolCar = new PetrolCar(); petrolCar.FillUp(); petrolCar.LitresOfPetrolRemaining.Should().Be(50); petrolCar.Drive(100); petrolCar.LitresOfPetrolRemaining.Should().Be(40);
OK, that works well.
We receive a change request: Our application should also be able to model Diesel cars, and these must be usable wherever we currently accept PetrolCars
.
It’s time for us to do some designing. We could have a new type, DieselCar
. Since we want to be able to use PetrolCar
and DieselCar
interchangeably, we need a common abstraction, an interface or abstract class, that we shall call Car
.
Car
will contain the declarations for common methods and properties:
void FillUp()
void Drive(int kilometres)
int LitresOfPetrolRemaining
One of these looks a bit off. LitresOfPetrolRemaining
is fine for PetrolCar
but doesn’t work well for DieselCar
:
var dieselRemaining = dieselCar.LitresOfPetrolRemaining; // ?!?
OK, we need a common abstraction that will work for PetrolCar
and DieselCar
. How about LitresOfFuelRemaining
?
Car petrolCar = new PetrolCar(); petrolCar.FillUp(); var petrol = petrolCar.LitresOfFuelRemaining; Car dieselCar = new DieselCar(); dieselCar.FillUp(); var diesel = dieselCar.LitresOfFuelRemaining;
That works.
Electric cars are becoming popular, and requirements have changed again. We have been asked to modify the system to include electric cars. We will include a new type, ElectricCar
, that extends abstraction Car
.
Electric cars do not use liquid fuel the way petrol and Diesel cars do. For propulsion, they use electric charge stored in batteries. The existing abstraction in Car
does not work for ElectricCar
:
var electricCharge = electricCar.LitresOfFuelRemaining; // ?!?
Yuch—that will not do. We will need to come up with a better abstraction that works for all types of Car
s.
How about PercentOfFuelRemaining
?
Yes, that may work. Fuel is still fuel even when we are holding it in electric charge. Using percentages (0% – empty, 100% – full) instead of litres allows us to use all fuel types. Nice!
Let’s see how it works out with the different kinds of Car
models:
Car petrolCar = new PetrolCar(); petrolCar.FillUp(); petrolCar.PercentOfFuelRemaining.Should().Be(100); petrolCar.Drive(100); petrolCar.PercentOfFuelRemaining.Should().Be(80); Car dieselCar = new DieselCar(); dieselCar.FillUp(); dieselCar.PercentOfFuelRemaining.Should().Be(100); dieselCar.Drive(300); dieselCar.PercentOfFuelRemaining.Should().Be(60); Car electricCar = new electricCar(); electricCar.FillUp(); electricCar.PercentOfFuelRemaining.Should().Be(100); electricCar.Drive(200); electricCar.PercentOfFuelRemaining.Should().Be(50);
It can be tricky to design an abstraction that works well with all specific types. However, we can usually achieve it by reflecting on the common aspects of the various particular implementations.
Leave a Reply
Want to join the discussion?Feel free to contribute!