我遇到过许多反对在C#中包含多重继承的论据,其中一些包括(除了哲学论证):
多重继承过于复杂,而且往往含糊不清
这是不必要的,因为接口提供类似的东西
如果接口不合适,组合是一个很好的替代品
我来自C++背景,错过了多重继承的力量和优雅.虽然它不适合所有软件设计,但在某些情况下很难否认它在接口,组合和类似的OO技术上的实用性.
是否排除了多重继承,说开发人员不够聪明,不能明智地使用它们,并且在它们出现时无法解决这些复杂问题?
我个人欢迎将多重继承引入C#(也许是C##).
附录:我很想知道来自单一(或程序背景)与多重继承背景的回复.我经常发现,没有多重继承经验的开发人员通常会默认使用多继承是不必要的参数,因为他们没有任何关于范例的经验.
我从来没有错过过一次,也从未错过过.是的,它[MI]变得复杂,是的,接口在很多方面做了类似的工作 - 但这不是最重要的一点:在一般意义上,大多数时候根本不需要它.在许多情况下,甚至单一继承也被过度使用.
更喜欢继承聚合!
class foo : bar, baz
通常可以更好地处理
class foo : Ibarrable, Ibazzable { ... public Bar TheBar{ set } public Baz TheBaz{ set } public void BarFunction() { TheBar.doSomething(); } public Thing BazFunction( object param ) { return TheBaz.doSomethingComplex(param); } }
通过这种方式,您可以交换进出IBarrable和IBazzable的不同实现来创建应用程序的多个版本,而无需编写另一个类.
依赖注入可以帮助解决这个问题.
处理多重继承的一个问题是接口继承和实现继承之间的区别.
通过使用纯接口,C#已经有了一个干净的接口继承实现(包括隐式或显式实现的选择).
如果你看一下C++,对于每一个类,你在冒号后指定class
申报,继承时获得的是由访问修饰符确定(private
,protected
,或public
).通过public
继承,您可以获得多重继承的完全混乱 - 多个接口与多个实现混合在一起.通过private
继承,您只需实现." class Foo : private Bar
" 的对象永远不会传递给期望a的函数,Bar
因为它就好像Foo
该类实际上只有一个私有Bar
字段和一个自动实现的委托模式.
纯多重实现继承(实际上只是自动委托)不存在任何问题,并且在C#中很棒.
对于类的多接口继承,有许多不同的可能设计来实现该功能.每个具有多重继承的语言都有自己的规则,以确定在多个基类中使用相同名称调用方法时会发生什么.有些语言,如Common Lisp(特别是CLOS对象系统)和Python,有一个元对象协议,您可以在其中指定基类优先级.
这是一种可能性:
abstract class Gun { public void Shoot(object target) {} public void Shoot() {} public abstract void Reload(); public void Cock() { Console.Write("Gun cocked."); } } class Camera { public void Shoot(object subject) {} public virtual void Reload() {} public virtual void Focus() {} } //this is great for taking pictures of targets! class PhotoPistol : Gun, Camera { public override void Reload() { Console.Write("Gun reloaded."); } public override void Camera.Reload() { Console.Write("Camera reloaded."); } public override void Focus() {} } var pp = new PhotoPistol(); Gun gun = pp; Camera camera = pp; pp.Shoot(); //Gun.Shoot() pp.Reload(); //writes "Gun reloaded" camera.Reload(); //writes "Camera reloaded" pp.Cock(); //writes "Gun cocked." camera.Cock(); //error: Camera.Cock() not found ((PhotoPistol) camera).Cock(); //writes "Gun cocked." camera.Shoot(); //error: Camera.Shoot() not found ((PhotoPistol) camera).Shoot();//Gun.Shoot() pp.Shoot(target); //Gun.Shoot(target) camera.Shoot(target); //Camera.Shoot(target)
在这种情况下,在冲突的情况下,只隐式继承第一个列出的类的实现.必须明确指定其他基类型的类才能获得它们的实现.为了使它更具有傻瓜性,编译器可以在冲突的情况下禁止隐式继承(冲突方法总是需要强制转换).
此外,您可以使用隐式转换运算符在C#中实现多重继承:
public class PhotoPistol : Gun /* ,Camera */ { PhotoPistolCamera camera; public PhotoPistol() { camera = new PhotoPistolCamera(); } public void Focus() { camera.Focus(); } class PhotoPistolCamera : Camera { public override Focus() { } } public static Camera implicit operator(PhotoPistol p) { return p.camera; } }
它并不完美,不过,因为它不是由支持is
和as
运营商,以及System.Type.IsSubClassOf()
.
这是我一直遇到的多重继承的一个非常有用的案例.
作为工具包供应商,我无法更改已发布的API,或者我将破坏向后兼容性.由此产生的一件事是,一旦我发布它就不能添加到接口,因为它会破坏实现它的任何人的编译 - 唯一的选择是扩展接口.
这对现有客户来说很好,但新的会看到这个层次结构不必要复杂,如果我从一开始就设计它,我不会选择以这种方式实现它 - 我必须,否则我会失去向后兼容性.如果接口是内部的,那么我只需添加它并修复实现者.
在许多情况下,接口的新方法有一个明显的小默认实现,但我不能提供它.
我更喜欢使用抽象类,然后当我必须添加一个方法时,添加一个带有默认实现的虚拟方法,有时我们会这样做.
当然,问题在于,如果这个类可能会混合到已经扩展的东西中 - 那么我们别无选择,只能使用接口并处理扩展接口.
如果我们认为我们有很大的问题,我们选择一个丰富的事件模型 - 我认为这可能是C#中的正确答案,但不是每个问题都以这种方式解决 - 有时候你需要一个简单的公共接口对于扩展者来说,还有一个更丰富的.
C#支持单继承,接口和扩展方法.在它们之间,它们提供了多重继承所提供的所有内容,而没有多重继承带来的麻烦.
我不知道CLR是否支持多重继承,因此我怀疑它是否能够以有效的方式得到支持,就像在C++中一样(或者Eiffel,考虑到语言是专门设计的,它可以做得更好)为MI).
多重继承的一个很好的替代方法叫做Traits.它允许您将各种行为单元混合到一个类中.编译器可以支持traits作为单继承类型系统的编译时扩展.您只需声明类X包含特征A,B和C,并且编译器将您要求的特征放在一起以形成X的实现.
例如,假设您正在尝试实现IList(T).如果你看一下IList(T)的不同实现,它们通常会共享一些完全相同的代码.这就是特征.你只需要在其中声明一个包含公共代码的特征,并且可以在IList(T)的任何实现中使用该公共代码 - 即使实现已经有其他基类.这是语法的样子:
/// This trait declares default methods of IListpublic trait DefaultListMethods : IList { // Methods without bodies must be implemented by another // trait or by the class public void Insert(int index, T item); public void RemoveAt(int index); public T this[int index] { get; set; } public int Count { get; } public int IndexOf(T item) { EqualityComparer comparer = EqualityComparer .Default; for (int i = 0; i < Count; i++) if (comparer.Equals(this[i], item)) return i; return -1; } public void Add(T item) { Insert(Count, item); } public void Clear() { // Note: the class would be allowed to override the trait // with a better implementation, or select an // implementation from a different trait. for (int i = Count - 1; i >= 0; i--) RemoveAt(i); } public bool Contains(T item) { return IndexOf(item) != -1; } public void CopyTo(T[] array, int arrayIndex) { foreach (T item in this) array[arrayIndex++] = item; } public bool IsReadOnly { get { return false; } } public bool Remove(T item) { int i = IndexOf(item); if (i == -1) return false; RemoveAt(i); return true; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } IEnumerator GetEnumerator() { for (int i = 0; i < Count; i++) yield return this[i]; } }
你使用这样的特征:
class MyList: MyBaseClass, DefaultListMethods { public void Insert(int index, T item) { ... } public void RemoveAt(int index) { ... } public T this[int index] { get { ... } set { ... } } public int Count { get { ... } } }
当然,我只是在这里摸索表面.有关更完整的描述,请参阅论文特征:可组合行为单位(PDF).
Rust语言(来自Mozilla)以一种有趣的方式实现了Traits:他们注意到traits类似于默认接口实现,因此他们将接口和traits统一为单个功能(他们称之为traits).traits和默认接口实现(Java现在拥有)之间的主要区别在于traits可以包含private或protected方法,这与传统的必须公开的接口方法不同.如果特征和接口没有统一到一个特征中,那么另一个区别是你可以引用一个接口,但你不能引用一个特征; 特质本身不是一种类型.
实际上,由于某个特定原因,我错过了多重继承......处理模式.
每次我需要实现dispose模式时,我都会对自己说:"我希望我可以从一个实现带有一些虚拟覆盖的dispose模式的类派生出来." 我将相同的样板代码复制并粘贴到每个实现IDispose的类中,我讨厌它.
我只是因为你陈述的原因而反对多重继承.开发人员会滥用它.我已经看到从实用程序类继承的每个类都有足够的问题,因此你可以从每个类调用一个函数而不需要输入那么多,知道多重继承会在很多情况下导致错误的代码.关于GoTo可以说同样的事情,这是它使用的原因之一是如此不受欢迎.我认为多重继承确实有一些很好的用途,就像GoTo一样.在一个理想的世界里,只有在恰当的时候使用它们才会有问题.然而,世界并不理想,所以我们必须保护坏程序员.
是!是!是的!
说真的,我整个职业生涯都在开发GUI库,MI(多重继承)使这个FAR比SI(单继承)更容易
首先我用C++ 做了SmartWin ++(MI大量使用),然后我做了Gaia Ajax,最后做了Ra-Ajax,我可以非常自信地说MI管理某些地方.其中一个地方是GUI库......
并且声称MI"过于复杂"的论点大多是由那些试图构建语言战争并且恰好属于"目前没有MI"的阵营的......
就像函数式编程语言(如Lisp)已经被非函数式编程语言倡导者教授("非Lispers")一样"过于复杂"......
人们害怕未知......
MI规则!