为什么继承和多态性被广泛使用?


18

我越了解不同的编程范例(例如函数式编程),就越开始质疑OOP概念(如继承和多态性)的智慧。我最初在学校学习继承和多态性,当时,多态性似乎是编写允许轻松扩展的通用代码的绝妙方法。

但是面对鸭子类型(动态和静态)以及诸如高阶函数之类的功能特性,我已经开始将继承和多态性视为基于对象之间脆弱的一组关系施加了不必要的限制。多态性背后的一般想法是,您只需编写一次函数,以后便可以在不更改原始函数的情况下向程序中添加新功能-您要做的就是创建另一个实现必要方法的派生类。

但是,无论是使用动态语言(例如Python)还是使用静态语言(例如C ++),通过鸭子输入都可以轻松实现这一点。

例如,请考虑以下Python函数,后跟等效的静态C ++:

def foo(obj):
   obj.doSomething()

template <class Obj>
void foo(Obj& obj)
{
   obj.doSomething();
}

相当于OOP的类似于以下Java代码:

public void foo(DoSomethingable obj)
{
  obj.doSomething();
}

当然,主要区别在于Java版本需要先创建接口或继承层次结构,然后才能起作用。因此,Java版本涉及更多工作,并且灵活性较差。此外,我发现大多数现实世界中的继承层次结构都有些不稳定。我们都已经看到了人为的形状和动物的示例,但是在现实世界中,随着业务需求的变化和新功能的添加,很难真正完成任何工作,然后才能真正扩展子类,或者重新建模/重构层次结构以包含更多的基类或接口,以适应新的需求。使用鸭子类型,您无需担心要建模的任何事情-您只需要担心所需的功能

但是,继承和多态性是如此流行,以至于我怀疑将它们称为可扩展性和代码重用的主要策略是否过于夸张。那么,为什么继承和多态性如此成功呢?我是否忽略了继承/多态性比鸭子类型具有的一些重要优势?


我不是Python专家,所以我不得不问:如果obj没有doSomething方法会发生什么?是否引发例外?没事吗
FrustratedWithFormsDesigner

6
@Frustrated:提出了一个非常明确的特定异常。
dsimcha 2011年

11
难道鸭子类型只是多态性就占了11个?我的猜测是,您不会对OOP感到沮丧,而会对它的静态类型化身感到沮丧。我确实可以理解,但这并不意味着整个OOP都不是一个好主意。

3
首先阅读“广泛”为“疯狂” ...

Answers:


22

我大体上同意你的观点,但为了娱乐,我将扮演Devil's Advocate。显式接口为查找明确的,正式指定的合同提供了一个位置,告诉您类型应该做什么。当您不是项目的唯一开发人员时,这可能很重要。

此外,这些显式接口可以比鸭子类型更有效地实现。虚拟函数调用几乎不能比普通函数调用具有更多开销,只是它不能被内联。鸭子打字有很大的开销。C ++样式的结构类型化(使用模板)会产生大量的目标文件膨胀(因为每个实例化在目标文件级别都是独立的),并且当您在运行时需要多态性而不是编译时不起作用。

底线:我同意Java风格的继承和多态可以成为PITA,应该更经常使用替代方法,但是它仍然具有其优势。


29

继承和多态性由于对某些类型的编程问题有效而被广泛使用。

并不是说它们在学校受到广泛的教育,而是倒退:它们在学校得到广泛的教育,因为人们(又名市场)发现它们比旧工具工作得更好,于是学校开始教它们。[轶事:当我第一次学习OOP时,很难找到一所教授任何OOP语言的大学。十年后,很难找到一所没有教授OOP语言的大学。]

你说:

多态性背后的一般想法是,您只需编写一次函数,以后就可以在不更改原始函数的情况下向程序中添加新功能-您要做的就是创建另一个实现必要方法的派生类。

我说:

不,不是

您所描述的不是多态,而是继承。难怪您遇到了OOP问题!;-)

备份步骤:多态性是消息传递的好处;它只是意味着每个对象都可以以自己的方式自由响应消息。

所以... 鸭子打字(或者说是使能)多态

您问题的要旨似乎不是您不了解OOP或不喜欢OOP,而是您不喜欢定义接口。很好,只要您小心,一切都会正常进行。缺点是,如果您犯了一个错误-例如,省略了一个方法-您将在运行时才发现错误。

这是静态还是动态的事情,这与Lisp一样古老,并且不仅限于OOP。


2
+1为“鸭子打字多态性”。多态性和继承已在潜意识中链接在一起了太长时间,严格的静态类型化是它们必须要做的唯一真实原因。
cHao 2013年

+1。我认为静态与动态的区别不应该只是注解-它是鸭子类型和Java风格多态性之间区别的核心。
Sava B.

8

我已经开始将继承和多态性视为基于对象之间脆弱的一组关系施加了不必要的限制。

为什么?

继承(带有或不带有鸭子类型)可确保重用公共功能。如果很常见,则可以确保在子类中一致地重用它。

就这样。没有“不必要的限制”。这是一个简化。

类似地,“鸭型”就是多态。相同的方法。许多具有相同接口但实现方式不同的类。

这不是一个限制。这是一个简化。


7

继承被滥用,鸭子输入也被滥用。两者都会并且确实会导致问题。

使用强类型,可以在编译时完成许多“单元测试”。使用鸭子打字时,您通常必须编写它们。


3
您对测试的评论仅适用于动态鸭子输入。C ++样式的静态鸭子输入(带有模板)会在编译时给您带来错误。(尽管,很难理解授予的C ++模板错误,但这完全是一个不同的问题。)
Channel72 2011年

2

在学校学习事物的好处是,您确实可以学习它们。不太好的事情是,您可能过于宽容地接受了它们,却不了解它们什么时候有用,什么时候没用。

然后,如果您是教条式地学习,您以后可以像在另一个方向上以教条式“反抗”。那也不好。

与任何此类想法一样,最好采取务实的方法。对它们适合的地方和不适合的地方形成了解。并忽略它们被超卖的所有方式。


2

是的,静态类型和接口是限制。但是,自从结构化编程被发明以来(即“ goto认为是有害的”),所有事情都与约束我们有关。鲍勃叔叔在他的视频博客上对此有很好的理解

现在,有人可以说收缩是不好的,但另一方面,收缩使秩序,控制和熟悉感成为了本来非常复杂的话题。

通过(重新)引入动态类型甚至直接访问内存来放松约束是一个非常强大的概念,但是它也使许多程序员难以应对。尤其是程序员,过去大部分时间都依赖于编译器和类型安全性。


1

继承是两个类之间非常紧密的关系。我认为Java没有任何更强大的功能。因此,仅应在需要时使用它。公共继承是“是”关系,而不是“通常是”关系。过度使用继承非常容易,而且一团糟。在许多情况下,继承用于表示“具有”或“具有功能”,通常最好通过组合来完成。

多态性是“是”关系的直接结果。如果Derived从Base继承,则每个Derived“是-a” Base,因此,无论在何处使用Base,都可以使用Derived。如果这在继承层次结构中没有意义,则该层次结构是错误的,并且可能正在进行太多继承。

鸭子输入是一个很好的功能,但是如果您要滥用它,编译器不会警告您。如果您不想在运行时处理异常,则需要确保自己每次都能获得正确的结果。仅定义静态继承层次结构可能会更容易。

我不是静态类型的忠实拥护者(我认为它通常是过早优化的一种形式),但是它确实消除了一些错误类别,许多人认为这些类别值得消除。

如果您喜欢动态类型和鸭子类型比静态类型和定义的继承层次更好,那就很好。但是,Java方式确实具有其优势。


0

我注意到,使用C#闭包的次数越多,我执行传统OOP的次数就越少。继承曾经是轻松共享实现的唯一方法,因此我认为它经常被过度使用,并且概念界限被推得太远。

虽然通常可以使用闭包来完成继承中的大部分操作,但它也会变得很丑陋。

基本上,这是一种适合工作的情况:当您拥有适合的模型时,传统的OOP可以非常好地工作;而当您不适合时,闭包也可以非常好地工作。


-1

真相在中间。我喜欢C#4.0是静态类型语言如何通过“动态”关键字支持“鸭子类型”。


-1

即使从FP的角度来看,继承也是一个很好的概念。它不仅节省了大量时间,而且还使程序中某些对象之间的关系有意义。

public class Animal {
    public virtual string Sound () {
        return "Some Sound";
    }
}

public class Dog : Animal {
    public override string Sound () {
        return "Woof";
    }
}

public class Cat : Animal {
    public override string Sound () {
        return "Mew";
    }
}

public class GoldenRetriever : Dog {

}

这里的类GoldenRetriever具有相同SoundDog免费感谢的继承。

我将用我的Haskell级别编写相同的示例,以让您看到差异

data Animal = Animal | Dog | Cat | GoldenRetriever

sound :: Animal -> String
sound Animal = "Some Sound"
sound Dog = "Woof"
sound Cat = "Mew"
sound GoldenRetriever = "Woof"

在这里,你不要逃避其指定soundGoldenRetriever。一般来说,最简单的方法是

sound GoldenRetriever = sound Dog

但是,如果您有20个功能,只需成像即可!如果有Haskell专家,请向我们展示一种更简单的方法。

也就是说,同时具有模式匹配和继承会很棒,如果当前实例没有实现,则函数将默认为基类。


5
我发誓,Animal是OOP的反教程。
Telastyn

@Telastyn我在YouTube上从Derek Banas那里学到了这个例子。好老师。我认为这很容易可视化和推理。您可以随意使用更好的示例来编辑帖子。
Cristian Garcia 2014年

这似乎并没有给之前的10个答案带来任何实质性的
好处

@gnat尽管“物质”已经存在,但是刚接触该主题的人可能会发现一个更容易理解的示例。
Cristian Garcia 2014年

2
由于多种原因,动物和形状确实是多态性的不良应用,对此原因已在本网站上进行了讨论。基本上,说猫“是”动物是没有意义的,因为没有具体的“动物”。您真正要建模的意思是行为,而不是身份。定义可以实现为喵叫或吠叫的接口“ CanSpeak”是有意义的。定义正方形和圆形可以实现的接口“ HasArea”是有意义的,但不是通用的Shape。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.