4

The dependency Inversion principle says that (Head First Java):

  • Depend upon abstractions. Do not depend upon concrete classes.

What does it mean with respect to inheritance? As a subclass depends on concrete class.

I am asking for a case when say - There is an interface Bird (doesn't have fly method as some birds cant fly), which represents all Non-Flying Birds. So I create a class - NonFlyingBird which implements Bird.

Now I want to make a class for Birds that can fly. As NonFlyingBirds and FlyingBirds have the same attributes I extend the FlyingBirds from NonFlyingBirds and Implement Flyable to give it flying behavior.

Do Doesn't it break the dependency Inversion principle as FlyingBirds is extending from a concrete class NonFlyingBirds?

interface Bird {   // Represents non Flying birds

    void getColor();
    void getHeight();
    ...
 }



class NonFlyingBird implements Bird {
     void getColor();
     void getHeight();
      ...
  }


class FlyingBird extends NonFlyingBird implements Flyable { // Does it break Dependency Inversion principle by extending concrete NonFlyingBird class? 

    Flyable fly;
    ...
  }

Note - The only reason I am extending is because the FlyingBird has the same attributes and methods as NonFlyingBird + the flying behaviour. So it kind of makes sense to reuse code by inheritance.

lynxx
  • 544
  • 3
  • 18
  • 4
    Now every `FlyingBird` is also a `NonFlyingBird`, and you violated [Liskov's substitution principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle) – Johannes Kuhn Sep 27 '19 at 05:48
  • @JohannesKuhn But I cant think why substituting a `FlyingBird` with `NonFlyingBird` would cause any problem? – lynxx Sep 27 '19 at 05:59
  • 2
    Maybe not now, but next month comes the next intern and says "I know, I just check if it is a NonFlyingBird or a FlyingBird using `instanceof`" and will curse your decision. – Johannes Kuhn Sep 27 '19 at 06:02
  • @JohannesKuhn hmm, good Point. It appears to me that I need to create a two different classes for moving and non moving Birds. is it correct? – lynxx Sep 27 '19 at 06:06
  • You have some good answers, below, but the question is wrong. When you do something awful with inheritance, it's not a problem with inheritance. The problem is what you did with it. Don't do that. – Matt Timmermans Sep 28 '19 at 19:03

3 Answers3

2

Short answer: No.

Longer answer: Use strategy pattern.

Classes that depend on the Bird interface can still be handed a FlyingBird or NotFlyingBird instance without knowing the difference.

The interface for Bird is still the same, or in general, should be, if the classes do have a different interface (new methods in FlyingBird that your calling code depends on), then there is a problem.

Perhaps a better way to solve your problem is by using the Strategy Pattern.

Off the cuff example:

public interface Bird {

    void fly();
}

public class BirdImpl implements Bird {

    private FlightStrategy flightStrategy;

    public BirdImpl(FlightStrategy flightStrategy) {

        this.flightStrategy = flightStrategy;
    }

    public void fly() {

        this.flightStrategy.fly();
    }
}

public interface FlightStrategy {

    void fly();
}

public class FlyingBirdFlightStrategy implements FlightStrategy {

    public void fly() {

        System.out.println("Wings flap");
        System.out.println("Wings flap");
        System.out.println("Wings flap");
        System.out.println("Wings flap");
    }
}

public class NonFlyingBirdFlightStrategy implements FlightStrategy {

    public void fly() {

        // do nothing non flying birds can't fly.
    }
}

Then when creating your Bird for use, create the default BirdImpl and pass in the FlightStrategy for the type of bird you want.

hooknc
  • 4,854
  • 5
  • 31
  • 60
  • What about the birds that don't fly? How are they represented in this design? – lynxx Sep 27 '19 at 05:35
  • Well, good point. I guess the music quotes aren't great examples... I'll simplify. But basically, the fly method of a non flying bird, well would do nothing. A non flying bird can't fly. – hooknc Sep 27 '19 at 05:37
  • Is using empty methods any good? I read somewhere that It is a sign that I need to refactor. That's the whole point I came up with that architecture(inheritance). – lynxx Sep 27 '19 at 05:41
  • Well, it depends. In my opinion there isn't anything inherently wrong with empty, no behavior, methods. In this particular case, I feel as though it is the correct behavior. I would think that `fly()` is a pretty reasonable method that you would expect to find on an interface named `Bird`. And I think that the correct behavior for a flightless bird when you pick it up and toss it into the air (gently of course), is not to fly. – hooknc Sep 27 '19 at 05:49
2

Yes, your intuition is correct - inheritance introduces non breakable compile-time and run-time dependency between a child and its parent.

Therefore the applications of inheritance is limited: You should only use inheritance to implement an "is" relation, not a "has code to be shared" relation. You should be careful, though: inherit only when an object "is" its subclass during the whole life. Human can safely extend Mammal, but it's risky to define Student as a subclass of a Human. Humans can cease to be Students and you cannot change this in runtime.

class FlyingBird extends NonFlyingBird

This is a heresy! :)

There's a strong movement against template classes like "AbstractBird". Unfortunatelly, a lot of introductory programming books teach this pattern of code sharing. There's nothing inherently wrong with it, but nowadays there are better solutions - Strategy, Bridge. In Java, you can even have poor-man's Traits - default implementations in interfaces.

In my opinion, the Dependency Inversion Principle, when applied to Inheritance translates to "Favour composition over inheritance".

Mateusz Stefek
  • 3,478
  • 2
  • 23
  • 28
  • Note that Strategy and Bridge (and all the GoF patterns) are 30+ years old, so it's a bit strange to say, "_nowadays there are better solutions_". Those solutions have been around for decades. Totally agree that inheritance is a concrete dependency, though. See what I did there with "is a"? – jaco0646 Sep 27 '19 at 14:07
2

Despite I liked an example with strategy from answers there, I will answer too, because I think you are confused a bit about dependency inversion principle.

Dependency inversion principle means

Don't use this, if you don't really need LinkedList behaviour:

public class A {
    private LinkedList<SomeClass> list;
//...
}

Use that instead:

public class A {
    private List<SomeClass> list; //or even use Collection or Iterable
//...
}

Dependencies are what we use inside our class.

Inheritance

Inheritance is what we call IS-A relationship and it has nothing to do with the principle. If you want to make class A that inherits class B, you need to answer a question: is it true that A is B. If you ask this question, you'll find that expression "FlyingBird is a NonFlyingBird" is a noncense.

Reusing code with inheritance

Think about this: not all birds can fly and not only birds(e.g. flies) can fly. It may lead us to an idea that we should create interface Flyable as you already done. Then we should rename NonFlyingBird to just SimpleBird, because if some creature is a bird it doesn't mean it can fly. In the end you will get:

class FlyingBird extends SimpleBird implements Flyable { 

    void fly() {
        ...
    }
    ...
}

Hope it'll help.

  • Can you please explain, 'not all birds can fly and not only birds can fly.'? – lynxx Sep 27 '19 at 10:29
  • @lynxx, sorry, I wrote it in a convoluted way. I updated the post. – Aleksandr Semyannikov Sep 27 '19 at 11:15
  • In my opinion, neither of these examples, including the ones in the question have anything to do with dependency injection. Dependency injection works by specifically avoiding the requirement of having to know the implementation of a dependency. SimpleBird in this case isn't an interface, but a specific kind of bird. And although Flyable is an interface, it merely defines the requirements of FlyingBird. But Flyable is not a reference to an object that implements the fly function/Flyable interface as that would be the case with DI. – The19thFighter Jan 04 '23 at 14:09