何时使用继承?

4
我和我的朋友有一个小争论。 我需要实现一个"浏览器进程监视器"类,每当被监视的浏览器(比如Internet Explorer)运行时,该类将调用一个事件。
我们创建了一个“进程监视器”类,争论从这里开始:
他说构造函数只应接受字符串(比如"iexplore.exe"),而我则认为我们应该继承“进程监视器”来创建一个“浏览器监视器”,它接受当前使用的浏览器枚举,构造函数将把它“翻译”成“iexplore.exe”。 他说我们应该使用一个实用函数作为翻译器。
我知道两种方式都是有效的和好的,但我想知道每种方式的优缺点以及在我们的情况下哪种更合适。

虽然我晚了将近7年才来评论,但我还是想留下我的看法。无论哪种情况,都不要使用继承。如果你想将ProcessWatcher用作某种BrowserWatcher工厂,则将其注入构造函数并将其设置为私有成员变量。这样做更好,因为你的东西不是ProcessWatcher,并且没有必要将这些API附加到你的类上(即使你的东西确实是一个进程监视器并且你想要这些API,你也应该使用组合而不是继承)。 - weberc2
7个回答

12

最近,我采取的方法是“现在保持简单,如果需要扩展再进行重构。”

你现在正在做的似乎相当简单。你只需要处理一个情况。所以我建议现在采用更简单的方法。最终,如果你从来不需要再创建另一种类型的监视器,则可以避免额外的复杂性。但要以一种容易重构的方式编码。

将来,如果你发现需要另一种类型的监视器,请花费精力将其重构为继承(或组合,或其他模式)。如果初始代码编写正确,重构应该相对容易,因此你并没有增加太多额外的工作量。

我发现这种方法对我来说相当有效。在我真正不需要继承的情况下,代码保持简单。但当我真正需要时,我可以轻松添加它而没有任何实际的问题。


2
我有一个倾向,经常试图过度概括事物,因此这种方法对我非常有效,因为它可以防止我在不需要时这样做。 - Herms

6

其他条件相同的情况下,我更喜欢简单的解决方案(一个以字符串为构造函数参数的具体类)而不是更复杂的方案(使用基类和子类)。

当您想要改变行为时,继承是适当的:如果浏览器监视器将执行一些普通进程监视器不会执行的操作。但是,如果您只想改变数据的值,则只需改变数据即可。


4
如果你只是想让ProcessWatcher作为BrowserWatcher的父类,那么你就不需要创建它。如果正在实现其他具有共享功能的Watchers,可以将这些功能放在ProcessWatcher中(这两者都是“isa”关系,因此符合Rob的标准)。
就是这么简单。争论说“某一天”你会有其他的watchers并不是创建一个单独的类的理由。这是一个你应该尽快改掉的思维定势。

另一方面,为ProcessWatcher创建一个接口被广泛认为是使您的应用程序更加灵活的好方法。我引用《代码大全》作为我的参考。 - rmeador
虽然我通常不会与麦康奈尔争论,但我认为我需要一个重要的论据来解释这如何使应用程序更具灵活性。如果有一天我们需要一个类似功能的第二个类,那么我会看到抽象类/接口,但在此之前不需要。 - Mark Brittingham
顺便说一句:如果你有这方面的论点,请务必在此处输入答案,因为我很想看看!如果您有cc2e.com网站上的参考资料,也请指出。我来这里既是为了学习,也是为了教授知识,所以被证明是错误的也无妨。 - Mark Brittingham

2
继承只能用于实现“isa”关系。
如果你可以说“浏览器监视器”是“进程监视器”的一个特定实例,那么继承就适用于这种架构。
因此,对我来说,通过将正在观看的对象的身份作为“进程监视器”的浏览器监视器实现的一部分传递是绝对正确的方式。
编辑:更具体地说,继承是用于专门化行为的。例如,大多数动物都会发出声音,但你可以在名为动物的类中提供要发出的声音,你必须等待专门化。
然后我们有Horse类提供其声音的“neigh”,Dog类提供其声音的“bark”等。
希望对你有所帮助。
谢谢,
Rob

-1 是因为你在第二段中说“是一个特定的实例”。这个“特定的实例”部分表明它应该是 ProcessWatcher 的一个实例,而不是子类型。如果你能说“是 ProcessWatcher 的一种”,那么你就需要继承。这就是为什么我说“是一个”是一个糟糕的测试的原因。 - rmeador
虽然经常使用,但说“是一个”并不是一个很好的答案。你可以说瑞士军刀“是一个”很多东西 - 它是一把刀,一个开罐器,一个螺丝刀,一个螺旋塞,或者你可以说它有一把刀片(这是一个切割边缘),一个短而尖的东西(这是一个开罐器)等等。 - Pete Kirkham

1

这取决于您的使用情况或您所追随的神。

我不会说“继承是邪恶的”,但通常我遵循“优先选择组合而非继承”的原则,以避免过多的类层次结构。


0

用非常简单的语言来说:

当你需要使用继承(子类化)时,子类具有与超类不同的行为(而不是属性)。


0

我认为在大多数情况下,简单胜过复杂是一个好策略,只要你的简单不是太短视(参考Herms的建议,在编写代码时要以便于后期重构为目标)。

然而,我也知道要消除脑海中那个鼓励更彻底设计的错误思想是多么困难。如果你仍然想支持继承,但不一定要考虑“基类”和“子类”,你可以简单地定义一个接口(例如IProcessWatcher),由ProcessWatcher实现。当你使用ProcessWatcher对象时,用接口来引用它,这样如果你以后决定创建一个BrowserWatcher(或任何其他类型的ProcessWatcher),只要它实现了IProcessWatcher接口,就不必强制它从ProcessWatcher派生。

警告:请谨慎操作。很容易想要为每个对象定义一个接口,但我们必须面对这是荒谬的事情。=)

最终,你需要找到一些你们都感到舒适的东西,因为你们两个都必须维护这段代码,我认为这可能是一个不错的妥协方案,而不仅仅是“继承还是不继承”的问题。

祝你好运!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接