为何以及何时使用多态?

lom*_*do2 7 c# oop polymorphism

我是OOP的新手,多态性让我很难过:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这样打印

Dog eating
Animal eating
Run Code Online (Sandbox Code Playgroud)

但是为什么不使用Dog类型代替动物,比如Dog dog = new Dog()?当你知道这个物体是一种动物,但我不知道它是什么样的动物时,我认为这很方便.请向我解释一下.

谢谢

Mik*_*keB 9

您可以通过其超类引用子类.

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());
}
Run Code Online (Sandbox Code Playgroud)


Dav*_*vid 7

当你想要一些不关心具体实现的一般方法,而只关注总体类型时,多态性真的很方便.使用你的动物例子:

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();
}
Run Code Online (Sandbox Code Playgroud)

请注意,该方法并不关心它是什么类型的动物,它只是试图喂它.也许这样可以Dog实现Eat()吞噬视线中的一切.也许Cat()实施它,它咬了一口,走开了.也许Fish()它实现它吃得太多而且死了.该方法本身并不关心Animal它得到了什么,您可以轻松添加更多Animal类型,而无需更改接受它们的方法.

(与此相关的是战略模式.)

相反,有时您需要一种方法来返回一般类型,而不管实现了什么.我使用的一个常见例子是:

public interface AnimalRepository
{
    IEnumerable<Animal> GetAnimals();
}
Run Code Online (Sandbox Code Playgroud)

实际上,这以两种方式使用多态.首先,Animal它返回的s 的枚举可以是任何类型.在这种情况下,任何调用代码都不关心哪一个,它将以更一般的方式使用它们(例如在前面的例子中).此外,IEnumerable可以返回任何实现的东西.

所以,例如,我有一个使用LINQ to SQL的接口的实现:

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

这会返回一个IQueryable.然而,无论什么称呼这种方法,都不关心它是什么IQueryable.它只会使用这些功能IEnumerable.

或者,我有另一个模拟测试实现:

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

    public IEnumerable<Animal> GetAnimals()
    {
        return animals;
    }
}
Run Code Online (Sandbox Code Playgroud)

这是返回一个IList,它再次被多态化为更一般,IEnumerable因为这是所有调用代码将使用.

这些也被称为协方差和逆变.在返回IEnumerable上面的类型的情况下,类型从更具体(IQueryableIList)移动到更通用(IEnumerable).他们能够在没有转换的情况下执行此操作,因为更具体的类型也是类型层次结构中更通用类型的实例.

与此相关的还有Liskov替换原则,该原则声明类型的任何子类型都可以用作该父类型,而无需对程序进行更改.也就是说,如果Dog是的子类型Animal,那么你应该总是能够用Dog作为Animal,而不必知道它是一个Dog或作出任何特殊考虑它.

您也可以从查看依赖性倒置原则中受益,上述存储库实现可以作为示例.正在运行的应用程序不关心哪个type(AnimalRepositoryImplementation)实现接口.它关心的唯一类型是界面本身.实现类型可以具有附加的公共或至少内部方法,实现组件使用这些方法来实现如何实现特定依赖性,但这些方法对于消费代码没有任何影响.每个实现都可以随意交换,调用代码只需要提供更通用接口的任何实例.


旁注:我个人发现继承经常被过度使用,特别是普通的继承,就像Animal示例中的Animal本身不应该是可实例化的类一样.如果应用程序的逻辑需要通用形式,它可以是接口或抽象类.但不要仅仅是为了做到这一点.

一般来说,根据Gang Of Four一书的推荐,更喜欢构图而不是继承.(如果您没有副本,请获取副本.)不要过度使用继承,但请在适当的地方使用它.如果将常用功能分组到组件中并且每个组件都是由这些组件构建的,那么应用程序可能更有意义吗?当然,更常用的例子可以从中吸取教训.AnimalAnimalCar

保持逻辑上定义的类型.你应该写new Animal()吗?拥有一个Animal不再具体的通用实例是否有意义?当然不是.但是,拥有应该能够在任何Animal(进料,复制,模具等)上运行的通用功能可能是有意义的.