我应该使用继承吗?

时间:2010-07-22 18:12:09

标签: oop inheritance class-design

这更像是一个主观问题,所以我会先发制人地将其标记为社区维基。

基本上,我发现在我的大多数代码中,有许多类,其中许多类互相使用,但很少有相互直接相关的类。我回顾一下我的大学时代,并想到传统的class Cat : Animal类型示例,其中有大量的继承树,但我在代码中看不到这一点。我的班级图看起来像巨型蜘蛛网,不像漂亮的漂亮树木。

我觉得我在逻辑上分离信息方面做得很好,最近我通过DI / IoC技术很好地隔离了类之间的依赖关系,但是我担心我可能会遗漏一些东西。我确实倾向于在接口中聚集行为,但我根本不是子类。

我可以很容易地理解传统示例(例如class Dog : Animalclass Employee : Person)的子类化,但我根本没有任何明显的我正在处理的事情。事情很少像class Label : Control那样明确。但是当我将代码中的实体实体建模为层次结构时,我不知道从哪里开始。

所以,我想我的问题归结为:

  1. 可以简单地不进行子类化或继承吗?我应该关心吗?
  2. 您有哪些策略可以确定可以从继承中受益的对象?
  3. 是否可以根据行为(接口)而不是实际类型继承?

11 个答案:

答案 0 :(得分:10)

继承应始终代表“is-a”关系。如果A来自B,你应该可以说“A是B”。如果不是,则更喜欢作曲。在没有必要的情况下不进行子类化是完全没问题的。

例如,说FileOpenDialog“是一个”Window是有道理的,但是说Engine“是一个”Car是无稽之谈。在这种情况下,Engine实例中的Car实例更合适(可以说Car“是”Engine实现的“{{1}} )。

有关继承的详细讨论,请参阅gotw.ca上的“{3}}和Part 1”使用和滥用继承“。

答案 1 :(得分:3)

  1. 只要你没有错过明确的'是'关系,那就没关系,事实上,最好不要继承,而是要使用作文。

  2. is-a是试金石。 if(是X是Y?)然后是class X : Y { }其他class X { Y myY; }class Y { X myX; }

  3. 使用接口,即继承行为,是通过仅添加所需行为而不是其他行为来构造代码的非常简洁的方法。棘手的部分是很好地定义这些接口。

答案 2 :(得分:3)

不应该使用任何技术或模式。您显然在类中往往不能从继承中受益的域中工作,因此您不应该使用继承。

你已经使用DI来保持整洁干净。您将课程的关注点分开了。这些都是好事。如果你真的不需要它,不要试图强制继承。

这个问题的一个有趣的后续问题是:哪些编程域确实倾向于充分利用继承? (UI和db框架已经被提及并且是很好的例子。还有其他吗?)

答案 3 :(得分:2)

我也讨厌狗 - >哺乳动物 - >动物的例子,正是因为它们不会发生在现实生活中。

我使用的子类很少,因为它将子类紧密地耦合到超类,并使您的代码难以阅读。有时实现继承很有用(例如PostgreSQLDatabaseImpl和MySQLDatabaseImpl扩展AbstractSQLDatabase),但大多数时候它只是乱七八糟。大多数时候,我看到子类,这个概念被误用了,应该使用接口或属性。

然而,界面很棒,你应该使用它们。

答案 4 :(得分:2)

通常,赞成组合而不是继承。 继承往往会破坏封装。例如如果一个类依赖于超类的方法,并且超类在某个版本中更改了该方法的实现,则子类可能会中断。

在您设计框架时,您将 设计要继承的类。如果要使用继承,则必须仔细记录和设计。例如不在构造函数中调用任何实例方法(可以被子类覆盖)。此外,如果它是一个真正的'is-a'关系,继承是有用的,但如果在一个包中使用它会更强大。

Effective Java(第14和15项)。它给出了一个很好的论据,说明为什么你应该支持组合而不是继承。它讲述了一般的继承和封装(使用java示例)。因此,即使您不使用java,它也是一个很好的资源。

回答你的3个问题:

可以简单地不进行子类化或继承吗?我应该关心吗? 答:问问自己,问题是它真的是“是一种”关系吗?装修可能吗?去装饰

// A collection decorator that is-a collection with 
public class MyCustomCollection implements java.util.Collection {
    private Collection delegate;
    // decorate methods with custom code
}

您有哪些策略可以确定可以从继承中受益的对象? Ans:通常在编写框架时,您可能希望提供某些专门为继承而设计的接口和“基础”类。

始终基于行为(接口)而不是实际类型继承是否可以接受? 答:大多数情况下是的,但如果超级类是为继承和/或在你的控制下设计的,你会更好。或者去寻找作曲。

答案 5 :(得分:1)

恕我直言,你永远不应该做#3,除非你专门为此目的建立一个抽象基类,它的名字清楚地说明它的目的是什么:

class DataProviderBase {...}
class SqlDataProvider : DataProviderBase {...}
class DB2DataProvider : DataProviderBase {...}
class AccountDataProvider : SqlDataProvider {...}
class OrderDataProvider : SqlDataProvider {...}
class ShippingDataProvider : DB2DataProvider {...}

同样遵循这种类型的模型,有时如果你提供一个接口(IDataProvider),最好也提供一个基类(DataProviderBase),未来的消费者可以使用它来方便地访问通用的逻辑。应用程序模型中的所有/大多数DataProviders。

作为一般规则,我只使用继承,如果我有一个真正的“is-a”关系,或者它会改善我的整体设计创建一个“is-a” “关系(例如,提供者模型。)

答案 6 :(得分:1)

如果你有共享功能,那么编程到接口比继承更重要。

基本上,继承更多的是将对象关联在一起。

大多数时候,我们关心的是物体可以做什么,而不是它是什么。

class Product
class Article
class NewsItem

NewsItemArticle两个Content项目都是?也许,您可能会发现能够拥有包含Article项和NewsItem项的内容列表很有用。

但是,你可能更有可能让它们实现类似的接口。例如,IRssFeedable可以是它们都实现的接口。实际上,Product也可以实现这个界面。

然后,他们都可以轻松地投入RSS Feed,以提供您网页上的内容列表。当interface很重要而继承模型可能不太有用时,这是一个很好的例子。

继承就是要识别对象的本质 接口都是关于识别对象可以做什么。

答案 7 :(得分:1)

我的类层次结构也趋于相当平坦,接口和组合提供了必要的耦合。当我存储事物的集合时,似乎会突然出现继承,其中不同类型的事物将具有共同的数据/属性。当存在共同数据时,继承通常对我来说更自然,而界面是表达共同行为的非常自然的方式。

答案 8 :(得分:0)

你的3个问题的答案都是“它取决于”。最终,它将取决于您的域名以及您的程序使用它做什么。很多时候,我发现我选择使用的设计模式实际上有助于找到继承运行良好的点。

例如,考虑用于将数据按摩成所需形式的“变换器”。如果您将3个数据源作为CSV文件,并希望将它们放入三个不同的对象模型(并可能将它们保存到数据库中),您可以创建一个'csv transformer'基础,然后在继承它时覆盖某些方法为了处理不同的特定对象。

将开发过程“转换”为模式语言将帮助您找到行为相似的对象/方法,并帮助减少冗余代码(可能通过继承,可能通过使用共享库 - 最适合的情况)。 / p>

此外,如果您将图层分开(业务,数据,演示文稿等),您的类图将更简单,然后您可以“可视化”那些应该继承的对象。

答案 9 :(得分:0)

我不会太担心你的班级图看起来如何,事情很少像教室......

而是问自己两个问题:

  1. 您的代码是否有效?

  2. 维护是否非常耗时?更改有时需要在许多地方更改“相同”代码吗?

  3. 如果对(2)的回答是肯定的,你可能想看看你是如何构建代码的,看看是否有更明智的方式,但要记住,在一天结束时,你需要能够对问题(1)回答“是”...不起作用的漂亮代码对任何人都没有用,并且难以向管理层解释。

答案 10 :(得分:0)

恕我直言,使用继承的主要原因是允许在基类对象上运行的代码对派生类对象进行操作。