C#动静结合编程之四:泛型委托
2010-09-30 20:50:04 来源:WEB开发网多态
多态是什么?一句话:接口和实现的1:n映射。多态让程序能通过统一的接口(广义的接口,意指规范)调用不同的实现,从而增强程序的表达能力和灵活性。我们最为熟悉的多态形式是包括接口继承在内的类型多态:
var animals = new List<IAnimal>() {
new Cat("Missy"),
new Cat("Mr. Bojangles"),
new Dog("Lassie")
};
foreach (var animal in animals) {
Console.WriteLine(animal.Name + ": " + animal.Talk());
}
有时,我们常常混淆继承与多态之间的关系,甚至认为二者是同一概念的不同方面。这里我尝试理清二者之间的区别与联系。在C#中,继承有两方面的功能:1.派生类复用基类的非私有成员和方法;2.实现类型多态。功能1的本质是“复用”,即派生类通过继承复用基类代码,基类与派生类之间处于同一抽象层次;功能2的本质是“被复用”,即派生类通过继承以基类/接口的名义被复用,基类与派生类是抽象与具体的关系。
我们常常把“基类”(base class)随意称作“父类”,不加区别,这在概念上是值得商榷的。“父子关系”是什么?儿子继承父亲的某些特征,强调的是复用,儿子与父亲是同一抽象层次的事物,只体现了继承的复用功能。“动物和狗的关系”显然不同于“父子关系”,动物与狗是抽象与具体的关系,程序中建立这种关系的主要目的在于以抽象的名义被复用,而非复用基类功能。因此,当继承的主要目的是复用时,我们可以把同一抽象层次上的基类称作“父类”,而当继承的主要目的是实现类型多态被复用时,“父类”的提法是欠妥的。
正是由于对继承功能两个方面欠缺清晰的认识,导致所谓的“滥用继承”。前文已经谈到过,继承是很强的类型约束,当目的只是为了复用时,如果采用继承的方式,往往会带来一些副作用,比如:在C#中,对象在其生命周期内无法改变继承关系,儿子像老子就必须像一辈子。其实复用并非OO的专利,在几乎所有场合,我们都可以通过其他方式实现复用,比如组合方式。因此,这里给出一条避免滥用继承的明确建议:当目的是复用,请考虑采用组合,而非继承!
泛型委托
多态与继承没有必然的联系,继承只是实现类型多态的一种手段。我理解,凡是符合接口与实现的1:n映射都属于多态。那么C#中的委托是不是多态呢?当然是,我姑且称它为方法多态,委托的调用者通过统一的委托调用不同的方法实现。与继承多态相比,委托消除了类型约束,凡是符合签名的方法都可以被委托调用,因此更加灵活。还有,泛型是不是多态呢?也是。泛型类/方法的编写者,通过使用统一的类型参数T表达程序的行为,把T具体化的工作交给泛型类/方法的用户。
委托是多态,泛型是多态,那么泛型委托是不是多态呢?当然更是,而其是非常强大的多态!其强大就在于融合了泛型和委托的抽象能力,又不失静态类型的安全性。GoF早就曾意识到泛型与委托相结合将是多么强大的抽象能力啊!而有幸的是,C#早已在语言层面直接支持泛型委托(据我所知,C++ boost库也提供了类似泛型委托的泛型函数指针)。
赞助商链接