当你想要一个不关心具体实现而只关心总体类型的通用方法时,多态真的派上用场了。使用您的动物示例:
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();
}
请注意,该方法并不关心它得到什么样的动物,它只是试图喂它。也许Dog
实现Eat()
了它吞噬了所有的东西。也许Cat()
实现它,让它咬一口然后走开。也许Fish()
实现它以至于它吃得太多而死。方法本身并不关心Animal
它得到什么,您可以轻松添加更多Animal
类型,而无需更改接受它们的方法。
(与此相关的是策略模式。)
相反,有时您希望方法返回通用类型,而不管实现了什么。我使用的一个常见示例是:
public interface AnimalRepository
{
IEnumerable<Animal> GetAnimals();
}
实际上,这以两种方式使用多态性。首先,它返回的 s 的枚举Animal
可以是任何类型。在这种情况下,任何调用代码都不会关心哪个是哪个,它将以更一般的方式使用它们(例如在前面的示例中)。此外,任何实现的东西IEnumerable
都可以返回。
因此,例如,我有一个使用 LINQ to SQL 的接口实现:
public class AnimalRepositoryImplementation : AnimalRepository
{
public IEnumerable<Animal> GetAnimals()
{
return new DBContext().Animals;
}
}
这将返回一个IQueryable
. 然而,无论调用该方法,都不关心它是一个IQueryable
. 它只会使用IEnumerable
.
或者,我有另一个模拟测试的实现:
public class AnimalRepositoryImplementation : AnimalRepository
{
private IList<Animal> animals = new List<Animal>();
public IEnumerable<Animal> GetAnimals()
{
return animals;
}
}
这将返回一个IList
,它再次被变形为更通用IEnumerable
的,因为这就是所有调用代码将要使用的。
这些也称为协变和逆变。在返回IEnumerable
上面的情况下,类型从更具体的 ( IQueryable
and IList
) 移动到更通用的 ( IEnumerable
)。他们无需转换就能做到这一点,因为更具体的类型也是类型层次结构中更通用类型的实例。
与此相关的还有Liskov Substitution Principle,它指出类型的任何子类型都可以用作该父类型,而无需更改程序。也就是说,如果 aDog
是 a 的子类型,Animal
那么您应该始终能够将 aDog
作为a 使用,Animal
而不必知道它是 aDog
或对其进行任何特殊考虑。
您还可以从Dependency Inversion Principle中受益,上面的存储库实现可以作为示例。正在运行的应用程序不关心哪个类型 ( AnimalRepositoryImplementation
) 实现了接口。它关心的唯一类型是接口本身。实现类型可能具有额外的公共或至少内部方法,实现程序集使用这些方法来确定特定依赖项是如何实现的,但这对使用代码没有影响。每个实现都可以随意换出,调用代码只需要提供更通用接口的任何实例。
旁注:我个人发现继承经常被过度使用,特别是简单的继承,Animal
例如在Animal
本身不应该是可实例化类的示例中。如果应用程序的逻辑需要通用形式,它可能是接口或抽象类。但不要仅仅为了做它而做它。
一般来说,正如《四人组》一书所推荐的,更喜欢组合而不是继承。(如果您没有副本,请获取一份。)不要过度使用继承,但要在适当的地方使用它。如果将通用功能分组为组件并且每个组件都由这些组件构建,那么应用程序可能会更有意义吗?当然,更常用的例子可以从中吸取教训。Animal
Animal
Car
保持你的类型在逻辑上定义。你应该能够写作new Animal()
吗?Animal
拥有一个不再具体的通用实例是否有意义?当然不是。但是拥有应该能够在任何Animal
(馈送、复制、死亡等)上运行的通用功能可能是有意义的。