OOP: "extends is evil"

http://www.javaworld.com/javaworld/jw-08-2003/jw-0801-toolbox.html?page=1

This is an interesting, but old article. I’d like some opinions from you wise sitepointers.

While I agree with the sentiment, and the purist in me agrees with the ideas behind it, from a practical point of view it seems ridiculous.

Even a simple piece of code like this:


class Car {
	protected $manufacturer;
	public $wheels = 4;
	public $doors = 4;

	public function __construct(Manufacturer $manufacturer) {
		$this->manufacturer= $manufacturer;		
	}

}


class HatchBack extends Car {
	public $doors = 2;
}

The “extends is evil” people would suggest replacing it with this:



interface Car {
	public function __construct(Manufacturer $manufacturer);
}



class GenericCar implements Car {
	protected $manufacturer;
	public $wheels = 4;
	public $doors = 4;

	public function __construct(Manufacturer $manufacturer) {
		$this->bar = $manufacturer;		
	}

}


class HatchBack implements Car {
	protected $manufacturer;
	public $wheels = 4;
	public $doors = 4;

	public function __construct(Manufacturer $manufacturer) {
		$this->bar = $manufacturer;		
	}
}

The problem is immediately obvious: repeated code. Repeated code is bad because it may change. As I see it, if the interface constructor ever changed then every class which implements would need to be updated to compensate. Using inheritance, only the base class has to be changed and all child classes are instantly effected.

I can appreciate the arguments it makes but the solution of using interfaces is not a good one! This can be achieved with traits too but they still count as tight coupling.

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:

namespace JeffMott;

class Car
{
    public function someStandardCarMethod()
    {}

    public function jeffsFancyCustomMethod()
    {}
}
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.

$myHatchBack = new HatchBack();

$myHatchBack->jeffsFancyCustomMethod();

Because now it’s harder to switch to newer or better car implementations.

namespace TomB;

class Car
{
    public function someStandardCarMethod()
    {
        // performs 50% faster
    }

    public function tomsFancyCustomMethod()
    {}
}
// switched to Tom's car implementation
use TomB\\Car;

class HatchBack extends Car
{
    public function someHatchBackMethod()
    {}
}
$myHatchBack = new HatchBack();

// this now breaks
$myHatchBack->jeffsFancyCustomMethod();

The alternative that the article seems to suggest is to use composition and interfaces.

namespace SomeVendor;

interface CarInterface
{
    public function someStandardCarMethod();
}
namespace TomB;

use SomeVendor\\CarInterface;

class Car implements CarInterface
{
    public function someStandardCarMethod()
    {
        // performs 50% faster
    }

    public function tomsFancyCustomMethod()
    {}
}
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.

Thanks! Your code example certainly made it clearer how to get around using extends. As you say, it’s a huge downside though. You cant even fudge it in PHP and use __call() because the method has to be defined due to the interface.

It is an interesting concept, and I can certainly see the downsides it mentions but in the real world it’s just unworkable. An even bigger downside is that if your someStandardCarMethod() is modified to take a parameter, every single type of car needs to be modified to take that parameter. Using the base class, it’s done throughout (With the exception of classes which override the method).

Repeated code creates maintainability issues. I think that’s my biggest problem with the “extends is evil” argument. Extends is probably less evil than repeated code :stuck_out_tongue: I may try to alter my code base to remove extends, just to see how well it works and how easy it is to remove it. Just as an experiment. I don’t think it will be easy… already I’m thinking how much work it would be for my data mapper!

In all honesty, I think the argument “extends is evil” is there to inform others to think before you write your code. Does extends make sense or is implements a better option? Maybe even a combination of the two. There are good reasons for Interfaces (mocking for example is very easy with interfaces for testing purposes). Most base classes should be marked abstract if you plan to extend them and not use the base class itself except to define common properties and methods (so you can create an instance of the base class itself). I personally believe a combination of extends and implements is appropriate in “most” cases and I use a combination of the two in the majority of my projects for my objects.

When/If you ever get into API design, interfaces are a good way to enforce plugin architecture, and having a base abstract class expose internal workings that you may want to impose (such as forcing a particular execution workflow). You can also then validate that each plugin follows the rules to by checking the object is an instanceOf your abstract class and that it implements your interface (very convenient).

Remember for every reason someone states NOT to do something, there is usually a good reason to do it. The question is, which one does your scenario fall into.