I'll start by saying that I'm immediately dubious of any article that resorts to such a hyperbole title. That being said, the general theme of the article seems to be an emphasis on composition and strategy patterns in situations where you would normally extend.
Normal inheritance might go like this:
PHP Code:
namespace JeffMott;
class Car
{
public function someStandardCarMethod()
{}
public function jeffsFancyCustomMethod()
{}
}
PHP Code:
use JeffMott\Car;
class HatchBack extends Car
{
public function someHatchBackMethod()
{}
}
A problem arises when users of HatchBack start to rely on this one particular base car implementation.
PHP Code:
$myHatchBack = new HatchBack();
$myHatchBack->jeffsFancyCustomMethod();
Because now it's harder to switch to newer or better car implementations.
PHP Code:
namespace TomB;
class Car
{
public function someStandardCarMethod()
{
// performs 50% faster
}
public function tomsFancyCustomMethod()
{}
}
PHP Code:
// switched to Tom's car implementation
use TomB\Car;
class HatchBack extends Car
{
public function someHatchBackMethod()
{}
}
PHP Code:
$myHatchBack = new HatchBack();
// this now breaks
$myHatchBack->jeffsFancyCustomMethod();
The alternative that the article seems to suggest is to use composition and interfaces.
PHP Code:
namespace SomeVendor;
interface CarInterface
{
public function someStandardCarMethod();
}
PHP Code:
namespace TomB;
use SomeVendor\CarInterface;
class Car implements CarInterface
{
public function someStandardCarMethod()
{
// performs 50% faster
}
public function tomsFancyCustomMethod()
{}
}
PHP Code:
use TomB\Car;
use SomeVendor\CarInterface;
class HatchBack implements CarInterface
{
// hatchback now "has-a" rather than "is-a" car
private $car;
public function __construct()
{
$this->car = new Car();
}
// the car interface is implemented using whichever car implementation we choose to use
public function someStandardCarMethod()
{
return $this->car->someStandardCarMethod();
}
public function someHatchBackMethod()
{}
}
The benefit is that if HatchBack ever needed to change car implementations, then that's easier to do, because that dependency is limited to within the private scope of HatchBack.
But there's a drawback too. In the example above, there's only one method in the car interface, but in a real-world application, there could be dozens. "extends" allows us to automatically inherit those dozens of methods. But insisting on only composition means manually re-implementing that interface, and not just for the HatchBack, but for every kind of car.
Personally, that sounds to me like a big downside, and I suspect this may be the reason why, nearly ten years later, the "extends is evil" idea hasn't really caught on.
Bookmarks