Hey Andrew.
You are right about everything you wrote.
Designing is difficult
and fun
What I can say is that first you are very correct when you write interfaces and not classes (implementations).
It is always good to think about objects as interfaces for a whole lot of reasons such as encapsulation, modulation,...
Regarding the specific Animal example:
I think that one should not put a method into an interfaces if the method does not belong to it.
It is logical and will help you not breaking LSP later.
If an Animal interface would have a tail and not all animals that extends it would have a tail, such as humans and bears, how could humans and bears wag the tail ?
If you would like all animals that have a tail (not including humans, bears, ...) to be able to wag their tail, than:
Create an interface that identifies all the animals that have a tail and give some methods to use the tail.
Example:
Interface Tailed extends Animal {
void wagTail();
void raiseTail();
void lowerTail();
}
interface Dog extends Tailed {
// Dog's specific methods
}
class DogImpl implements Dog {
public void wagTail() {
//implementation...
}
//Other methods implementations...
}
Now consider the case of having a collection of animals and you the ones with a tail to wag it.
One solution would be to traverse the animals and for each animal ask if it is Tailed (animal instance of Tailed).
If so, you can cast the animal to Tailed and invoke tailed.wagTail().
This solution is ok but have a big disadvantage of using instance of.
It is a good practice to avoid instance of whenever possible.
Another solution can be to use a visitor.
With a visitor you need to write a bit more code and it needs to know all of the types in compilation type but i think is more strong and more modular.
Usually, one would have at least one method in a base interface, unless it is a markup interface.
Hope this was helpful.
Regards,
Alik.