这是一个颇具争议的话题,在你说"不"之前,真的,真的需要吗?
我已经编程了大约10年,而且我不能诚实地说,我可以回想起继承解决了一个无法用另一种方式解决的问题.另一方面,当我使用继承时,我可以回想起很多次,因为我觉得我必须这样做,或者因为我虽然我很聪明并最终付出了代价.
我无法真正看到任何情况,从实现的角度来看,无法使用聚合或其他技术而不是继承.
我唯一需要注意的是,我们仍然允许继承接口.
(更新)
让我们举例说明为什么需要它而不是说"有时它只是需要它".这根本没有用.你的证明在哪里?
(更新2代码示例)
这是经典的形状示例,更强大,更明确的IMO,没有继承.现实世界中的情况几乎从来都不是真的"是一种"的东西.几乎总是"以实施方式"更准确.
public interface IShape { void Draw(); } public class BasicShape : IShape { public void Draw() { // All shapes in this system have a dot in the middle except squares. DrawDotInMiddle(); } } public class Circle : IShape { private BasicShape _basicShape; public void Draw() { // Draw the circle part DrawCircle(); _basicShape.Draw(); } } public class Square : IShape { private BasicShape _basicShape; public void Draw() { // Draw the circle part DrawSquare(); } }
Jon Skeet.. 20
我刚才在博客上写道这是一个古怪的想法.
我不认为它应该被删除,但我认为类应该默认密封,以阻止继承,当它不合适时.它是一个功能强大的工具,但它就像一个链锯 - 除非它是完美的工具,否则你真的不想使用它.否则你可能会开始失去四肢.
这是潜在的语言功能,例如混合,这将使其更容易生活,IMO.
我刚才在博客上写道这是一个古怪的想法.
我不认为它应该被删除,但我认为类应该默认密封,以阻止继承,当它不合适时.它是一个功能强大的工具,但它就像一个链锯 - 除非它是完美的工具,否则你真的不想使用它.否则你可能会开始失去四肢.
这是潜在的语言功能,例如混合,这将使其更容易生活,IMO.
在基类具有多个对每个派生类具有相同实现的方法的情况下,继承可能非常有用,以保存每个派生类不必实现样板代码.以.NET Stream
类为例,它定义了以下方法:
public virtual int Read(byte[] buffer, int index, int count) { } public int ReadByte() { // note: this is only an approximation to the real implementation var buffer = new byte[1]; if (this.Read(buffer, 0, 1) == 1) { return buffer[0]; } return -1; }
因为继承是可用的,所以基类可以ReadByte
为所有实现实现该方法,而无需担心它.在类上有许多其他类似于默认或固定实现的方法.所以在这种情况下,与一个界面相比,它是一个非常有价值的东西,你可以选择让每个人重新实现所有东西,或创建一个StreamUtil
他们可以调用的类型类(yuk!).
为了澄清,继承所有我需要写的创建DerivedStream
类是这样的:
public class DerivedStream : Stream { public override int Read(byte[] buffer, int index, int count) { // my read implementation } }
然而,如果我们使用接口和方法的默认实现,StreamUtil
我必须编写更多代码:
public class DerivedStream : IStream { public int Read(byte[] buffer, int index, int count) { // my read implementation } public int ReadByte() { return StreamUtil.ReadByte(this); } }
}
所以这不是一个更多的代码,而是在类上增加了几个方法,它只是编译器可以处理的不必要的样板.为什么要使实施比必要的更痛苦?我不认为继承是最重要的,但是如果正确使用它会非常有用.
当然,你可以在没有对象和继承的情况下愉快地编写出色的程序; 功能程序员一直这样做.但是,让我们不要仓促.任何对此主题感兴趣的人都应该查看Xavier Leroy邀请的关于Objective Caml中的类和模块的演讲的幻灯片.Xavier做了一个很好的工作,在不同类型的软件演化的背景下,确定了继承做得好并且做得不好.
所有语言都是图灵完备的,因此当然不需要继承.但作为继承价值的论证,我提出了Smalltalk蓝皮书,尤其是Collection层次结构和Number层次结构.令我印象深刻的是,熟练的专家可以在不影响现有系统的情况下添加全新的数字(或集合).
我还会提醒提问者"继承人的杀手级应用程序":GUI工具包.一个设计良好的工具包(如果你能找到)可以非常,非常容易地添加新的图形交互小部件.
说了这么多,我认为继承具有天生的弱点(你的程序逻辑被涂抹在一大堆类上),它应该很少使用,只能由熟练的专业人员使用.一个拥有计算机科学学士学位的人对继承知之甚少 - 应该允许这些人继承其他需要的课程,但绝不应该编写其他程序员继承的代码.这项工作应该留给那些真正知道自己在做什么的大师级程序员.他们应该不情愿地做到这一点!
对于使用完全不同的机制解决类似问题的有趣尝试,人们可能想要查看Haskell类型类.
我希望语言能提供一些机制,使其更容易委托给成员变量.例如,假设接口I有10个方法,C1类实现此接口.假设我想要实现类似C1的类C2,但是方法m1()被覆盖.在不使用继承的情况下,我会按照以下方式执行此操作(在Java中):
public class C2 implements I { private I c1; public C2() { c1 = new C1(); } public void m1() { // This is the method C2 is overriding. } public void m2() { c1.m2(); } public void m3() { c1.m3(); } ... public void m10() { c1.m10(); } }
换句话说,我必须明确地编写代码来将方法m2..m10的行为委托给成员变量m1.这有点痛苦.它也使代码混乱,因此很难在C2类中看到真正的逻辑.这也意味着无论何时向接口I添加新方法,我都必须向C1明确添加更多代码,以便将这些新方法委托给C1.
我希望语言允许我说:C1实现了我,但如果C1缺少某些方法,我会自动委托给成员变量c1.这会将C1的大小减少到恰好
public class C2 implements I(delegate to c1) { private I c1; public C2() { c1 = new C1(); } public void m1() { // This is the method C2 is overriding. } }
如果语言允许我们这样做,那么避免使用继承会容易得多.
这是我写的关于自动委派的博客文章.
继承是可以使用的工具之一,当然可以被滥用,但我认为语言必须有更多的更改才能删除基于类的继承.
让我们抓住我的世界,主要是C#开发.
为了让微软取消基于类的继承,他们必须为处理接口提供更强大的支持.像聚合这样的东西,我需要添加大量的样板代码,只是为了将接口连接到内部对象.无论如何,这确实应该完成,但在这种情况下是必需的.
换句话说,以下代码:
public interface IPerson { ... } public interface IEmployee : IPerson { ... } public class Employee : IEmployee { private Person _Person; ... public String FirstName { get { return _Person.FirstName; } set { _Person.FirstName = value; } } }
这基本上要短得多,否则我会有很多这些属性只是为了让我的课程模仿一个人足够好,这样的事情:
public class Employee : IEmployee { private Person _Person implements IPerson; ... }
这可以自动创建必要的代码,而不是我必须编写它.如果我将对象转换为IPerson,只返回内部引用就没有用了.
因此,在基于类的继承可以从表中取消之前,必须更好地支持这些事情.
此外,您将删除可见性等内容.一个界面真的只有两个可见性设置:那里,而不是那里.在某些情况下,您或者我认为,您将被迫公开更多内部数据,以便其他人可以更轻松地使用您的课程.
对于基于类的继承,您通常可以公开一些后代可以使用的访问点,但外部代码不能,并且您通常只需要删除这些访问点,或者让它们对所有人开放.不确定我是否喜欢其他选择.
我最大的问题是具体删除这些功能的重点是什么,即使计划是作为一个例子,构建D#,一种新的语言,如C#,但没有基于类的继承.换句话说,即使你计划建立一种全新的语言,我仍然不完全确定最终目标是什么.
目标是删除可能被滥用的东西,如果不是在正确的手中?如果是这样,我有一个一英里长的列表,用于各种编程语言,我真的希望首先看到地址.
在该列表的顶部:Delphi中的with关键字.这个关键词不仅仅是用脚射击自己,就像编译器购买霰弹枪一样,来到你家并瞄准你.
我个人喜欢基于类的继承.当然,你可以把自己写进一个角落.但我们都可以这样做.删除基于类的继承,我只会找到一种用脚射击自己的新方法.
我现在把那把霰弹枪放在哪里......
在所有类上实现ISystemObject非常有趣,这样您就可以访问ToString()和GetHashcode().
另外,ISystemWebUIPage接口祝你好运.
如果你不喜欢继承,我的建议是一起停止使用.NET.有太多的场景可以节省时间(参见DRY:不要重复自己).
如果使用继承会破坏您的代码,那么您需要退后一步并重新考虑您的设计.
我更喜欢接口,但它们不是银弹.