Search code examples
javadesign-patternsstrategy-pattern

Java Interface does not take into consideration method implementation from the super class


The following example with ducks, is based on the Head First design patterns book.

I have a game with different types of ducks. There is a super class Duck and it has two behaviours: fly and quack, which are stored in fields. The concrete classes decide (in the constructor) which behaviour a concrete breed has (see MalardDuck class).

I realised I want my ducks to not only have type Duck but also Quackable (so that I can have methods that accept only quackables - assuming that there are other types that quack - see Lake class). When I implement the interface in MallardDuck, the compiler complains that the class does not have the method quack although it is defined in its superclass - Duck class.

Now I could think of two solutions:

  1. Overwrite a method quack by just calling the method from the superclass: super.quack() <- but that seems unnecessary (in theory) - a child class has a direct access to superclass's public methods, so why the interface Quackable even complains...?
  2. Make Duck implement the Quackable -> this is rather illogical cause some Ducks don't Quack (their quackBehaviour is implemented with SiletnQuack class).

However in both solutions the duck:

  • HAS A quackable behaviour, AND
  • IS A quackable

Isn't that fundamentally wrong? What am I missing?

abstract class Duck{
    protected Flyiable flyBehaviour;
    protected Quackable quackBehaviour;
    public void quack(){
        quackBehaviour.quack();
    }
    void performFly(){
        flyBehaviour.fly();
    }
    void swim(){
        // swimming implementation
    }
    abstract void display();
}
interface Quackable{
    void quack();
}
class Quack implements Quackable{

    @Override
    public void quack() {
        System.out.println("Quack!");
    }
}
class Quack implements Quackable{

    @Override
    public void quack() {
        System.out.println("Quack!");
    }
}
class SilentQuack implements Quackable{

    @Override
    public void quack() {
        System.out.println("...");
    }
}
class MallardDuck extends Duck{
    public MallardDuck(){
        quackBehaviour = new Quack();
        flyBehaviour = new FlyWithWings();
    }

    @Override
    void display() {

        // it looks like a Mallard duck
    }
}

What if I want to accept ducks to this method as quackables (along with other animals):


class Lake{
    ArrayList<Quackable> quackingAnimals;
    void addQuackingAnimal(Quackable animal){
        quackingAnimals.add(animal);
    }
    void displayQuackables(){
        //...
    }
}

Solution 1:

class MallardDuck extends Duck implements Quackable {
    public MallardDuck(){
        quackBehaviour = new Quack();
        flyBehaviour = new FlyWithWings();
    }

    @Override
    void display() {

        // it looks like a Mallard duck
    }

    @Override
    public void quack() {
        super.quack();
    }
}

Solution 2:

abstract class Duck implements Quackable{
    protected Flyiable flyBehaviour;
    protected Quackable quackBehaviour;
    public void quack(){
        quackBehaviour.quack();
    }
    void performFly(){
        flyBehaviour.fly();
    }
    void swim(){
        // swimming implementation
    }
    abstract void display();
}

Solution

  • This is because Duck class DOES NOT implement the quack method from the Quackable interface. Although it has a quack method with the same signature, it is not the same method, that is declared in the interface.

    I do not understand, why Solution 2 (making the Duck class implement Quackable interface) would be illogical - it does expose public method for quacking, so all of it's descendants will quack anyway (but with different quack that is declared in the Quackable interface). In my opinion (only opinion), the Duck class should implement Quackable.

    If (in your case) not all Ducks quack, then it is reasonable, that a Duck can't be treated as something that has Quackable behavior and thus it can't be added to the collection of Quackable objects. In this case (in my opinion) you could create another abstract class extending Duck and implementing Quackable interface (like QuackingDuck) and in this case (in my opinion) you should remove quack method from Duck class - as not all Ducks quack.

    I hope that it answers your question. To sum up:

    • the quack method from Duck is not the implementation of the quack method from Quackable interface (as it would be the case in e.g. JavaScript)
    • in current implementation all Ducks quack, only their quacking behavior (implementation) is different (and in the case of SilentDuck - it still quacks)