How to do type hinting for interfaces?
After a short time, the manager decides to introduce to his fleet of cars a brand new Mercedes, but the problem is that the calcTankPrice() function can only perform calculations for BMWs. It turns out that, while BMWs have a rectangular shaped gas tank, Mercedes have a cylindrical shaped gas tank. So, our programmer is summoned once again to write another class that can handle this new task at hand. To this end, our programmer now writes the following class with the name of Mercedes that can calculate the tank volume for cylindrical shaped tanks.
class Mercedes {
protected $model;
protected $radius;
protected $height;
public function __construct($model, $radius, $height)
{
$this -> model = $model;
$this -> radius = $radius;
$this -> height = $height;
}
// Calculates the volume of cylinder
public function calcTankVolume()
{
return $this -> radius * $this -> radius * pi() * $this -> height;
}
}
When our programmer completed the task of writing the Mercedes class, he tried to calculate the price of a full tank of gas for a Mercedes.
$mercedes1 = new Mercedes('12189796', 7, 28);
echo calcTankPrice($mercedes1, 3);
However, the result wasn't quite what he had expected:
Result:
Catchable fatal error: Argument 1 passed to calcTankPrice() must be an instance of Bmw, instance of Mercedes given
This error message is the result of not passing the right object to the function, since he tried to pass a Mercedes object to the calcTankVolume() function whereas the function can only accept objects that belong to the Bmw class.
First, our programmer attempted to solve the problem by not using any type hinting, but then he understood that a better solution would be the use of type hinting for interfaces. Here I use interface in its wider meaning that includes both abstract classes as well as real interfaces.
Here I use interface in its wider meaning that includes both abstract classes as well as real interfaces.
So, first he created an abstract class with the name of Car that both the Bmw and the Mercedes (and, in fact, any other car model) can inherit from.
abstract class Car {
protected $model;
protected $height;
abstract public function calcTankVolume();
}
Then he re-factored the Bmw and Mercedes classes so that they inherit from the Car class:
class Bmw extends Car {
protected $rib;
public function __construct($model, $rib, $height)
{
$this -> model = $model;
$this -> rib = $rib;
$this -> height = $height;
}
// Calculates a rectangular tank volume
public function calcTankVolume()
{
return $this -> rib * $this -> rib * $this -> height;
}
}
class Mercedes extends Car {
protected $radius;
public function __construct($model, $radius, $height)
{
$this ->model = $model;
$this -> radius = $radius;
$this -> height = $height;
}
// Calculates the volume of cylinders
public function calcTankVolume()
{
return $this -> radius * $this -> radius * pi() * $this -> height;
}
}
The following diagram can help us to better understand the relation between the Car interface and the concrete classes (Bmw and Mercedes) that implement the interface for different gas tank shapes:
Since both the classes inherit from the same interface, he could comfortably type hint the function calcTankPrice() with the Car interface, so that the function can get any object as long as it belongs to this interface.
// Type hinting ensures that the function gets only objects
// that belong to the Car interface
function calcTankPrice(Car $car, $pricePerGalon)
{
echo $car -> calcTankVolume() * 0.0043290 * $pricePerGalon . "$";
}
Now, let's see the result when we try to use the function calcTankPrice() on both a Bmw and a Mercedes objects:
$bmw1 = new Bmw('62182791', 14, 21);
echo calcTankPrice($bmw1, 3);
$mercedes1 = new Mercedes('12189796', 7, 28);
echo calcTankPrice($mercedes1, 3);
Result:
53.454492$
55.977413122858$
Whenever we need to do type hinting to more than one related classes, we should be using interface type hinting.
The message to take home from this tutorial is that, whenever we need to do type hinting to more than one related classes, we should be using interface type hinting.
Programming to an interface makes the code much more flexible and ready for changes.
Another very important lesson that the above examples can teach us, is the importance of programming to an interface. When the programmer had made the calcTankPrice function dependent on the Bmw class, he restricted the class use to only one car type. But when he chose to make the function dependent on an interface he allowed the function to be used for any car type, and so made it much more flexible. From this we can conclude that programming to an interface makes the code much more flexible and ready for changes.