为什么使用inheritance?

我知道这个问题以前已经讨论过了 ,但是总是认为inheritance至less有时比组合更好。 我想挑战这个假设,希望得到一些理解。

我的问题是这样的: 因为你可以完成任何东西与对象组成,你可以用古典inheritance,因为古典inheritance经常被滥用[1] ,因为对象组成给你灵活地改变委托对象的运行时, 为什么你会用古典inheritance?

我可以理解为什么你会推荐一些像Java和C ++这样的语言来inheritance,这些语言没有提供方便的委托语法。 在这些语言中,只要不明显不正确,就可以使用inheritance来节省大量的input。 但是像Objective C和Ruby这样的其他语言既可以提供经典的inheritance,也可以为委派提供非常方便的语法。 Go编程语言是我所知的唯一的语言决定古典inheritance是比它的价值更麻烦,并且只支持委托代码重用。

另一种表述我的问题的方法是:即使你知道古典inheritance不是不正确的实现一个特定的模型,是否有足够的理由来使用它,而不是组成?

[1]许多人使用经典的inheritance来实现多态,而不是让他们的类实现一个接口。 inheritance的目的是代码重用,而不是多态。 此外,有些人使用inheritance来模拟他们对“一个”的关系的直观理解,这个关系往往是有问题的 。

更新

我只是想澄清我的意思,当我谈论inheritance:

我正在谈论一种类inheritance,从一个部分或完全实现的基类inheritance 。 我并不是在谈论从一个纯粹的抽象基类inheritance,这个基类和实现一个接口是一样的,而我所logging的这个接口并不是反对的。

更新2

我知道inheritance是实现C ++多态性的唯一方法。 在这种情况下,很显然你必须使用它。 所以我的问题仅限于诸如Java或Ruby之类的语言,它们提供了实现多态的独特方法(分别为接口和鸭子打字)。

如果你把没有被明确覆盖的所有东西都委托给实现相同接口的其他对象(“基本”对象),那么你基本上就是在构图上进行了Greenspunnedinheritance,但是在大多数语言中,和样板。 使用组合而不是inheritance的目的是让你只能委托你想要委派的行为。

如果你希望对象使用基类的所有行为,除非明确地被覆盖,那么inheritance是expression它的最简单,最不详细,最直接的方式。

inheritance的目的是代码重用,而不是多态。

这是你的根本错误。 几乎完全相反。 (公共)inheritance的主要目的是模拟有问题的类之间的关系。 多态性是很大一部分。

正确使用时,inheritance不是重用现有代码。 相反,它是关于被现有代码使用 。 也就是说,如果现有的代码可以与现有的基类一起工作,那么当您从现有的基类中派生一个新的类时,其他代码现在也可以自动使用新的派生类。

可以使用inheritance来重用代码,但是当/如果你这样做,通常应该是私有inheritance而不是公有inheritance。 如果你使用的语言支持委派,那么很可能你很less有很多理由使用私有inheritance。 OTOH,私有inheritance确实支持委托(通常)没有的一些事情。 特别是在这种情况下,即使多态性是一个次要问题,它仍然是一个问题 – 也就是说,对于私有inheritance,您可以从几乎是您想要的基类开始,并且(假设它允许)覆盖不完全正确的部分。

有了代表团,唯一真正的select就是完全按照现有的类来使用。 如果它不能完全满足你的要求,那么你唯一真正的select就是完全忽略这个function,并且从头开始重新实现它。 在某些情况下,这不是一个损失,但在其他情况下,这是相当可观的。 如果基类的其他部分使用多态函数,则私有inheritance允许您覆盖多态函数,其他部分将使用您的重写函数。 通过委派,您不能轻松插入新的function,因此现有基类的其他部分将使用您所覆盖的内容。

使用inheritance的主要原因不是作为组合的一种forms – 这是你可以得到多态的行为。 如果你不需要多态,你可能不应该使用inheritance,至less在C ++中。

在以下情况下,inheritance是首选:

  1. 您需要公开您所扩展类的整个API(使用委托,您需要编写大量的委托方法), 而且您的语言不提供简单的方式来说“委托所有未知方法”。
  2. 您需要访问没有“朋友”概念的语言的受保护字段/方法
  3. 如果您的语言允许多重inheritance,则委托的优点会有所降低
  4. 如果你的语言允许在运行时dynamic地inheritance一个类,甚至是一个实例,你通常根本不需要委托。 如果可以同时控制哪些方法暴露(以及如何暴露),则根本不需要它。

我的结论:授权是解决编程语言中的一个错误的方法。

每个人都知道多态是inheritance的一大优势。 我发现inheritance的另一个好处是有助于创build真实世界的复制品。 例如,在薪酬系统中,我们处理经理开发人员,办公室男生等,如果我们inheritance所有这些类与超级员工。 它使得我们的程序在现实世界中更容易理解,所有这些类都基本上是员工。 还有一件事类不仅包含它们也包含属性的方法。 因此,如果我们在Employee类中包含通用于Employee类的属性(如社会安全号码年龄等),则它将提供更高的代码重用性和概念清晰度,当然还有多态性。 然而,在使用inheritance的时候,我们应该牢记的是基本的devise原则“确定你的应用程序的不同方面,并将它们与那些改变的方面分开”。 你永远不应该实现那些由inheritance改变的应用的方面,而是使用组合。 而对于那些不可改变的方面,如果一个明显的“是”的关系所在,就应该使用inheritance的方式。

在使用inheritance之前,我总是三思而后行,因为它可能会变得很棘手。 这就是说有很多情况下,它只是产生最优雅的代码。

接口只定义了一个对象可以做什么,而不是如何做。 所以简单地说,接口就是合约。 所有实现这个接口的对象都必须定义他们自己的契约实现。 在实际的世界里,这给你separation of concern 。 想象一下,自己编写一个应用程序,需要先处理你不认识的各种对象,然而你需要处理它们,只有你知道的东西是这些对象应该做的所有不同的事情。 所以你要定义一个接口,并提到合同中的所有操作。 现在,您将针对该界面编写应用程序。 后来谁想要利用你的代码或应用程序将不得不实现对象的接口,使之与你的系统一起工作。 您的界面将强制他们的对象来定义合同中定义的每个操作应该如何完成。 这样,任何人都可以编写实现你的接口的对象,以便使它们完美地适应你的系统,而你所知道的只是需要做的事情,而且它是需要定义它如何完成的对象。

在现实世界的发展中,这种做法一般被称为Programming to Interface and not to Implementation

接口只是合同或签名,他们不知道任何有关实现。

对接口进行编码意味着,客户端代码始终保存由工厂提供的接口对象。 任何由工厂返回的实例都是typesInterface,任何工厂候选类都必须实现。 这样客户端程序并不担心实现,接口签名决定了所有的操作都可以完成。 这可以用来在运行时改变程序的行为。 它也可以帮助你从维护angular度写出更好的程序。

这是你的一个基本的例子。

 public enum Language { English, German, Spanish } public class SpeakerFactory { public static ISpeaker CreateSpeaker(Language language) { switch (language) { case Language.English: return new EnglishSpeaker(); case Language.German: return new GermanSpeaker(); case Language.Spanish: return new SpanishSpeaker(); default: throw new ApplicationException("No speaker can speak such language"); } } } [STAThread] static void Main() { //This is your client code. ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English); speaker.Speak(); Console.ReadLine(); } public interface ISpeaker { void Speak(); } public class EnglishSpeaker : ISpeaker { public EnglishSpeaker() { } #region ISpeaker Members public void Speak() { Console.WriteLine("I speak English."); } #endregion } public class GermanSpeaker : ISpeaker { public GermanSpeaker() { } #region ISpeaker Members public void Speak() { Console.WriteLine("I speak German."); } #endregion } public class SpanishSpeaker : ISpeaker { public SpanishSpeaker() { } #region ISpeaker Members public void Speak() { Console.WriteLine("I speak Spanish."); } #endregion } 

替代文字http://ruchitsurati.net/myfiles/interface.png

怎么样的模板方法模式? 假设您有一个基类,可以自定义策略, 但是至less有以下一个原因,策略模式是没有意义的:

  1. 可自定义策略需要知道基类,只能与基类一起使用,在其他任何情况下都没有意义。 相反,使用策略是可行的,但是一个PITA,因为基类和策略类都需要相互引用。

  2. 这些政策是相互耦合的,因为将它们自由混合搭配是没有意义的。 它们只在所有可能组合的非常有限的子集中才有意义。

你写了:

[1]许多人使用经典的inheritance来实现多态,而不是让他们的类实现一个接口。 inheritance的目的是代码重用,而不是多态。 此外,有些人使用inheritance来模拟他们对“一个”的关系的直观理解,这个关系往往是有问题的。

在大多数语言中,“实现接口”和“从另一个类派生类”之间的界限非常薄。 事实上,在像C ++这样的语言中,如果从类A派生出一个类B,并且A是一个只包含纯虚拟方法的类,那么您正在实现一个接口。

inheritance是关于接口重用 ,而不是实现重用 。 正如你上面写的那样,这不是关于代码重用。

inheritance,正如你正确指出的那样,是为了build立一个IS-A关系的模型(许多人认为这个错误与inheritance本身无关)。 你也可以说'如同一个'。 但是,仅仅因为某些东西与其他东西有一个IS-A关系,并不意味着它使用相同的(甚至相似的)代码来实现这种关系。

比较这个实现不同方式输出数据的C ++示例; 两个类使用(公共)inheritance,以便它们可以多态访问:

 struct Output { virtual bool readyToWrite() const = 0; virtual void write(const char *data, size_t len) = 0; }; struct NetworkOutput : public Output { NetworkOutput(const char *host, unsigned short port); bool readyToWrite(); void write(const char *data, size_t len); }; struct FileOutput : public Output { FileOutput(const char *fileName); bool readyToWrite(); void write(const char *data, size_t len); }; 

现在想象一下,如果这是Java。 “输出”不是结构,而是“接口”。 它可能被称为“可写”。 而不是“公共输出”,你会说“实现可写”。 就devise而言,有什么不同?

没有。

经典inheritance的主要用处在于,如果有多个相关的类,它们对于在实例variables/属性上运行的方法具有相同的逻辑。

有三种方法可以处理:

  1. 遗产。
  2. 复制代码( 代码气味 “重复的代码”)。
  3. 将逻辑移到另一个类(代码味道“懒惰类”,“中间人”,“消息链”和/或“不恰当的亲密关系”)。

现在,可能会有误用inheritance。 例如,Java有InputStreamOutputStream类。 这些子类用于读/写文件,套接字,数组,string,还有几个用于包装其他input/输出stream。 基于他们所做的,这些应该是接口而不是类。

我看到使用inheritance最有用的方法之一是在GUI对象中。

当你问:

即使你知道古典inheritance不是不正确的实现一个特定的模型,是否有足够的理由来使用它,而不是组成?

答案是不。 如果模型不正确(使用inheritance),则不pipe使用什么都是错误的。

以下是我见过的inheritance问题:

  1. 总是必须testing派生类指针的运行时types,以查看它们是否可以被强制(或者也可以被强制closures)。
  2. 这种“testing”可以通过各种方式来实现。 你可能有某种虚拟方法返回一个类标识符。 否则,你可能不得不实施RTTI(运行时间types标识)(至less在c / c ++),这可以给你一个性能打击。
  3. 没有得到“铸造”的类types可能会有问题。
  4. 有许多方法可以在inheritance树上向上或向下inheritance类types。

一些完全不是OOP的东西,但是,组成通常意味着一个额外的caching未命中。 这取决于数据越接近越好。

一般来说,我拒绝参加一些宗教斗争,用你自己的判断力和风格是你能得到的最好的。