Polymorphism is really useful when you want to have something like a general method that does not care about a specific implementation, but only about a comprehensive type. An example of using your animal:
public static void Main() { var animals = new List<Animal>(); animals.Add(new Dog()); animals.Add(new Cat()); foreach (var animal in animals) Feed(animal); } public static void Feed(Animal animal) { animal.Eat(); }
Note that the method does not matter what animal it receives, it will simply try to feed it. Maybe Dog implements Eat() so that it takes everything into view. Maybe Cat() implements it so that it bites and leaves. Maybe Fish() implements it in such a way that it eats and dies too much. The method itself does not care about which Animal it receives, and you can easily add more types of Animal without changing the method that accepts them.
(This applies to the Strategy Template .)
Conversely, sometimes you want a method to return a generic type no matter what was implemented. The usual example that I use is this:
public interface AnimalRepository { IEnumerable<Animal> GetAnimals(); }
It actually uses polymorphism in two ways. First, the Animal enumeration that it returns can be of any type. In this case, any calling code does not care about which one, and it will use them in a more general way (for example, in the previous example). In addition, anything that implements IEnumerable can be returned.
So, for example, I have an implementation of this interface that uses LINQ to SQL:
public class AnimalRepositoryImplementation : AnimalRepository { public IEnumerable<Animal> GetAnimals() { return new DBContext().Animals; } }
This returns an IQueryable . However, any method calls do not care that it is IQueryable . It will use functions on IEnumerable .
Or I have another implementation for prototyping:
public class AnimalRepositoryImplementation : AnimalRepository { private IList<Animal> animals = new List<Animal>(); public IEnumerable<Animal> GetAnimals() { return animals; } }
This returns an IList , which again undergoes polymorphism in a more general IEnumerable , because all calling codes will use.
They are also referred to as covariance and contravariance . If IEnumerable returns above, the types have moved from more specific ( IQueryable and IList ) to more general ( IEnumerable ). They were able to do this without conversion, because the more specific type is also an instance of the more typical type in the type hierarchy.
Also associated with this is the Liskov Substitution Principle , which states that any type subtype can be used as this parent type without requiring changes to the program. That is, if a Dog is a subtype of Animal , then you should always use Dog as Animal , not knowing that it is a Dog or make any special considerations for it.
You can also take a look at the dependency inversion principle , which can serve as an example of the repository implementation above. The launch of the application is not related to what type ( AnimalRepositoryImplementation ) implements the interface. The only type he cares about is the interface itself. Implementation types may have additional publicly available or at least internal methods that use implementation assemblies as to how this particular dependency is implemented, but which have nothing to do with the consumer code. Each implementation can be replaced as desired, and the call code should only be supplied with any instance of a more general interface.
Side note: I personally find that inheritance is often abused, in particular, regular inheritance, for example, in the Animal example, where Animal itself should not be an instance class. It can be an interface or an abstract class, perhaps if a general form is necessary for the application logic. But do not do this just for the sake of it.
In general, prefer composition over inheritance , as recommended by the Gang Of Four books . (If you do not have a copy, get it.) Do not abuse inheritance, but use it where necessary. Maybe the application would make more sense if the general functionality of Animal was grouped into components, and each Animal was built from these components? A more commonly used Car example might draw a lesson from this, of course.
Keep logically defined types. Should you ever write new Animal() ? Does it make sense to have a common instance of Animal that is not more specific? Of course not. But it makes sense to have universal functionality that should work on any Animal (feed, playback, die, etc.).