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:
However in both solutions the duck:
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();
}
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:
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)Ducks
quack
, only their quacking
behavior (implementation) is different (and in the case of SilentDuck
- it still quacks
)