SitePoint Sponsor

User Tag List

Results 1 to 4 of 4
  1. #1
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    988
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)

    OOP: "extends is evil"

    http://www.javaworld.com/javaworld/j...ox.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:

    PHP Code:
    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:

    PHP Code:

    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.

  2. #2
    SitePoint Wizard bronze trophy Jeff Mott's Avatar
    Join Date
    Jul 2009
    Posts
    1,148
    Mentioned
    14 Post(s)
    Tagged
    0 Thread(s)
    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.
    "First make it work. Then make it better."

  3. #3
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    988
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    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 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!

  4. #4
    Hosting Team Leader silver trophybronze trophy
    cpradio's Avatar
    Join Date
    Jun 2002
    Location
    Ohio
    Posts
    4,807
    Mentioned
    141 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by TomB View Post
    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
    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.


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •