Why and when use polymorphism?

本秂侑毒 提交于 2021-01-21 07:43:00

问题


I'm 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();
    }
}

So that prints

Dog eating
Animal eating

But why not to just use the Dog type instead of animal, like Dog dog = new Dog()? I assume this comes handy when you know the object is a animal, but don't know what kind of animal it is. Please explain this to me.

Thanks


回答1:


You can refer to a subclass by its super class.

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());
}



回答2:


Polymorphism really comes in handy when you want to have something like a general method which doesn't care about the specific implementation, but only about the overarching type. Using your animals example:

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 doesn't care what kind of animal it gets, it's just going to try to feed it. Maybe Dog implements Eat() such that it gobbles up everything in sight. Maybe Cat() implements it such that it takes a bite and walks away. Maybe Fish() implements it such that it eats too much and dies. The method itself doesn't care which Animal it gets, and you can easily add more Animal types without having to change that method which accepts them.

(Related to this is the Strategy Pattern.)

Conversely, sometimes you want a method to return a general type regardless of what's implemented. A common example I use is this:

public interface AnimalRepository
{
    IEnumerable<Animal> GetAnimals();
}

This uses polymorphism in two ways, actually. First, the enumeration of Animals that it returns can be of any type. In this case any calling code won't care which one is which, it's going to use them in a more general manner (such as in the previous example). Additionally, anything which implements IEnumerable can be returned.

So, for example, I have an implementation of this interface which uses LINQ to SQL:

public class AnimalRepositoryImplementation : AnimalRepository
{
    public IEnumerable<Animal> GetAnimals()
    {
        return new DBContext().Animals;
    }
}

This returns an IQueryable. Whatever calls the method, however, doesn't care that it's an IQueryable. It's only going to use the features on IEnumerable.

Or, I have another implementation for mock testing:

public class AnimalRepositoryImplementation : AnimalRepository
{
    private IList<Animal> animals = new List<Animal>();

    public IEnumerable<Animal> GetAnimals()
    {
        return animals;
    }
}

This is returning an IList, which again is polymorphed into the more general IEnumerable because that's all the calling code is going to use.

These are also referred to as covariance and contravariance. In the case of returning an IEnumerable above, the types moved from more specific (IQueryable and IList) to more generic (IEnumerable). They were able to do this without conversion because a more specific type is also an instance of a more generic type in the type hierarchy.

Also related to this is the Liskov Substitution Principle, which states that any subtype of a type can be used as that parent type without requiring alterations to the program. That is, if a Dog is a subtype of Animal then you should always be able to use a Dog as an Animal without having to know that it's a Dog or make any special considerations for it.

You may also benefit from looking into the Dependency Inversion Principle, which the above repository implementation can serve as an example. The running application isn't concerned with which type (AnimalRepositoryImplementation) implements the interface. The only type it cares about is the interface itself. The implementing types may have additional public or at least internal methods which the implementing assemblies use regarding how that particular dependency is implemented, but which are of no consequence to the consuming code. Each implementation can be swapped out at will and calling code need only be supplied with any instance of the more generic interface.


Side note: I personally find that inheritance is often overused, particularly plain inheritance like in the Animal example where Animal itself shouldn't be an instantiatable class. It can be an interface or an abstract class, perhaps, if the generic form is needed for the application's logic. But don't do it just for the sake of doing it.

In general, prefer composition over inheritance as recommended by the Gang Of Four book. (If you don't have a copy, get one.) Don't overuse inheritance, but do use it where appropriate. Maybe the application would make more sense if common Animal functionality was grouped into components and each Animal was built of those components? The more commonly-used Car example could take a lesson from that, for sure.

Keep your types logically defined. Should you ever be able to write new Animal()? Does it make sense to have a generic instance of Animal that's no more specific? Certainly not. But it may make sense to have generic functionality which should be able to operate on any Animal (feed, reproduce, die, etc.).




回答3:


The 'Animal' example of polymorphism is rather flawed if all you see is the hierarchy, as it seems to imply that every 'kind-of' relationship should be modeled polymorphically. This in turn leads to a lot of really convoluted code; base classes abound even when there is no compelling reason for them to be there.

The example makes a lot more sense when you introduce an object that uses the 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...
    }
}

Notice that your PetWalker encapsulates some shared functionality involved in walking any kind of Pet. What we've done is isolated only the Pet-specific behavior behind a virtual method. A Dog might pee on fire hydrants whereas an iguana might hiss at passers-by, but the act of walking them has been decoupled from what they do while walking.




回答4:


Because you should keep the common behavior in your base class and you override the behaviors that are different for a specific subclass. Thus you reduce code duplication.

You can still instantiate your object like Dog dog = new Dog() in your example. That has little to do with polymorphism. Though it's better to use the base class when you are trying to pass to any method that expects any animal, be it a human, dog, etc.




回答5:


It also allows you to pass in your super class to a method instead of the 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 end up with less code and easier maintainability.




回答6:


This comes in handy when you have multiple classes inheriting the same parent class or implementing the same interface. For example:

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"



回答7:


I'll provide a real and practical case where I actually used polymorphism. It is a particular case of what MikeB describes.

I want to tell this tale, because searching for Real Life example of Polymorphism renders a lot of isomorphisms with real life (like Dog -> Animal or Car -> Vehicle) and not enough REAL as in "I did actually write this code".

Now, before the tale, I want to mention that in most cases I use polymorphism to allow the extension of code by third parti developers. Say I create an interface for others to implement.


The tale

About six years ago I created an application to find routes on maps. It was a desktop application and it had to keep all the roads with their connections and be able to locate directions by street and number and find routes from one location to another. I want to note that I did never add real maps, all I used was hypotetical and designed for demostration purposes.

So I have a graph where the nodes where locations of the map. Every node had coordinates to position it on the map, but some where special for crossing of streets, building, and a few others.

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

One implementation for buildings and special locationst that should be possible to find them just by their name. Some where used to designate the crossing of roads (and therfore marking the start position to count house numbers when searching a particular direction*). And some others where just for presentation, allowing to describe the shape of the roads (because the roads aren't always straight lines).

*: Yes, I did actually write code to count them, instead of storing the number of each house. I was naive to think that storing the number of each house was naive. (or may be today I'm naive of thinking that... nevermind).

As for drawing, all that matters is where is the node and if I have to highlight it (Ah, yes, I did highlight them when the user hovers the pointer over the them), but for searching the other kinds of data where relevant.


A final note: In my experience, creating complex inheritance trees isn't helping maintenance at all. In particular if the derived class adds nothing respect to the base class or the derived class does not reuse code of the base class. And that is a big problem with the common examples, because it is clear in your Animal & Dog example that you gain nothing by making Dog inherit from Animal.



来源:https://stackoverflow.com/questions/12756048/why-and-when-use-polymorphism

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!