Why and when is polymorphism used?

I am new to OOP and polymorphism has given me a hard time:

class Animal { public virtual void eat() { Console.Write("Animal eating"); } } class Dog : Animal { public override void eat() { Console.Write("Dog eating"); } } class Program { public void Main() { Animal dog = new Dog(); Animal generic = new Animal(); dog.eat(); generic.eat(); } } 

To print

 Dog eating Animal eating 

But why not just use a dog type instead of an animal, like Dog dog = new Dog ()? I suppose this is convenient when you know that an object is an animal, but don’t know what animal it is. Please explain this to me.

thanks

+4
source share
7 answers

You can reference a subclass with your superclass.

 Animal dog = new Dog(); Animal cat = new Cat(); Animal frog = new Frog(); List<Animal> animals = new List<Animal>(); animals.add(dog); animals.add(cat); animals.add(frog); foreach(Animal animal in animals) { Console.WriteLine(animal.eat()); } 
+10
source

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.).

+4
source

An example of an Animal polymorphism is rather erroneous if all you see is a hierarchy, as this seems to imply that each β€œaffinity” must be modeled polymorphically. This, in turn, leads to a lot of really confusing code; base classes abound even when there is no good reason to be present.

An example makes much more sense when you enter an object that uses a hierarchy. For instance:

 public abstract class Pet { public abstract void Walk(); } public sealed class Dog : Pet { public override void Walk() { //Do dog things on the walk } } public sealed class Iguana : Pet { public override void Walk() { //Do iguana things on the walk } } public sealed class PetWalker { public void Walk(Pet pet) { //Do things you'd use to get ready for walking any pet... pet.Walk(); //Walk the pet //Recover from the ordeal... } } 

Please note that your PetWalker encapsulates some common functions related to walking any kind of Pet. What we did is isolated only from the behavior typical of Pet, beyond the virtual method. A dog can write on fire hydrants, while an iguana can hiss in passersby, but their movement was separate from what they do while walking.

+2
source

Because you must keep the general behavior in your base class and override the behavior that is different for a particular subclass. This way you reduce code duplication.

You can create your own object, for example Dog dog = new Dog () in your example. This has little to do with polymorphism. Although it is better to use the base class when you are trying to switch to any method that any animal expects, be it a person, a dog, etc.

+1
source

It also allows you to pass your superclass to a method instead of a subclass, for example:

 class GroomService { public void Groom(Animal animal) { animal.Groom(); } } public void Main() { GroomService groomService = new GroomService(); groomService.Groom(dog); groomService.Groom(generic); } 

The end result is that you get less code and simplify maintainability.

+1
source

This is useful if you have several classes that inherit the same parent class or implement the same interface. For instance:

 class Cat : Animal { public override void eat() { Console.Write("Cat eating"); } } class Program { public void Main() { Animal cat = new Cat(); Animal dog = new Dog(); cat.eat(); dog.eat(); } } 

This will output:

 "Cat eating" "Dog eating" 
0
source

I will give a real and practical case when I actually used polymorphism. This is a special case of what MikeB describes .

I want to tell this story because searching for an example of Real Life polymorphism makes many isomorphisms with real life (for example, Dog β†’ Animal or Car β†’ Vehicle) and not enough REAL, as in β€œI really wrote this code.”

Now, before the tale, I want to mention that in most cases I use polymorphism to allow third-party developers to extend the code. Let's say I'm creating an interface for others to implement.


Story

About six years ago, I created an application for finding routes on maps. It was a desktop application, and he had to support all the roads with his connections and be able to determine directions by street and number and find routes from one place to another. I want to note that I never added real maps, everything that I used was hypothetical and was intended for the purpose of demonstration.

So, I have a graph, where are the nodes, where are the maps. Each node had coordinates to position it on the map, but some where specifically for crossing streets, buildings and several others.

To do this, I used the interface for graph nodes, this interface allowed me to store and retrieve the coordinates and connections of the node. And I have various implementations of this interface.

One implementation for buildings and a special place where it should be possible to find them only by their name. Some, where it is used to indicate the intersection of roads (and, above all, the designation of the starting position for counting house numbers when searching for a certain direction *). And some others, where only for presentation, allowing to describe the shape of the roads (because the roads are not always straight).

*: Yes, I really wrote a code for counting, instead of storing the amount of each house. I naively thought that keeping the amount of each house was naive. (or maybe today, I'm naive, thinking that ... I never thought).

As for drawing, all that matters is the node somewhere, and if I need to select it (Ah, yes, I highlighted them when the user hovers over them), but to search for other types where appropriate.


Last remark. In my experience, creating complex inheritance trees doesn't help at all. In particular, if the derived class does not add anything to the base class, or the derived class does not reuse the code of the base class. And this is a big problem with common examples, because with the example of your animal and dog it is clear that you get nothing by making Dog inherit from Animal.

0
source

Source: https://habr.com/ru/post/1438116/


All Articles