なぜ継承とポリモーフィズムがそれほど広く使用されているのですか?


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バージョンはより多くの作業を必要とし、柔軟性が低くなります。さらに、ほとんどの実際の継承階層はやや不安定であることがわかりました。シェイプとアニマルの不自然な例を見てきましたが、現実の世界では、ビジネス要件の変更と新しい機能の追加に伴い、「is-a」関係を実際に伸ばす前に作業を完了することは困難です。サブクラス、または階層をリモデリング/リファクタリングして、新しい要件に対応するために、さらに基本クラスまたはインターフェイスを追加します。アヒルのタイピングでは、モデリングのことを心配する必要はありません- 必要な機能を心配するだけです。

しかし、継承とポリモーフィズムは非常に人気があるため、拡張性とコードの再利用のための支配的な戦略と呼ぶのは誇張ではないでしょうか。それでは、なぜ継承とポリモーフィズムはそれほど成功しているのでしょうか?継承/ポリモーフィズムがアヒルのタイピングよりも優れているいくつかの重大な利点を見落としていますか?


私はPythonの専門家ではないので、質問する必要objがあります。doSomethingメソッドがないとどうなりますか?例外が発生しましたか?何も起こりませんか?
FrustratedWithFormsDesigner

6
@Frustrated:非常に明確で特定の例外が発生します。
-dsimcha

11
アヒルのタイピングは多態性だけで11になりませんか?私の推測では、あなたはOOPではなく、静的に型付けされた化身に不満を感じています。私はそれを本当に理解できますが、それはOOP全体として悪い考えをしません。

3
最初に「広く」「ワイルドに」と読みます

回答:


22

私はほとんどあなたに同意しますが、楽しみのために悪魔の擁護者を演じます。明示的なインターフェイスは、明示的に正式に指定されたコントラクトを検索するための単一の場所を提供し、タイプが何をすべきかを示します。これは、プロジェクトの唯一の開発者ではない場合に重要になります。

さらに、これらの明示的なインターフェイスは、アヒルのタイピングよりも効率的に実装できます。仮想関数呼び出しは、インライン化できないことを除いて、通常の関数呼び出しよりもオーバーヘッドがほとんどありません。アヒルのタイピングにはかなりのオーバーヘッドがあります。C ++スタイルの構造型指定(テンプレートを使用)は、大量のオブジェクトファイルの膨張を生成することがあり(各インスタンス化はオブジェクトファイルレベルで独立しているため)、コンパイル時ではなく実行時にポリモーフィズムが必要な場合は機能しません。

結論:Javaスタイルの継承とポリモーフィズムはPITAになる可能性があり、代替手段をより頻繁に使用する必要があることに同意しますが、それにも利点があります。


29

継承とポリモーフィズムは、特定の種類のプログラミングの問題で機能するため、広く使用されています。

彼らは学校で広く教えられているわけではなく、逆です:人々(市場)が古いツールよりも優れていることを発見したため、学校で広く教えられているので、学校はそれらを教え始めました。[補遺:最初にOOPを学んだとき、OOP言語を教えている大学を見つけるのは非常に困難でした。10年後、OOP言語を教えていない大学を見つけることは困難でした。]

あなたが言った:

多態性の背後にある一般的な考え方は、一度関数を書くと、後で元の関数を変更せずにプログラムに新しい機能を追加できるということです-必要なのは、必要なメソッドを実装する別の派生クラスを作成することです

私は言う:

いいえ、そうではありません

あなたが説明するのはポリモーフィズムではなく、継承です。OOPに問題があるのも不思議ではありません!;-)

ステップをバックアップします。ポリモーフィズムはメッセージ受け渡しの利点です。それは単に、各オブジェクトが独自の方法でメッセージに自由に応答できることを意味します。

だから... ダックタイピング(またはむしろ、可能にする)ポリモーフィズムです

あなたの質問の要点は、OOPを理解していない、または嫌いだということではなく、インターフェイスを定義するのが好きではないようです。それは問題ありませんし、あなたが注意している限り、問題なく動作します。欠点は、間違えた場合-たとえばメソッドを省略した場合-実行時までわからないことです。

これは静的なものと動的なものです。これはLispと同じくらい古い議論であり、OOPに限定されません。


2
「ダックタイピングポリモーフィズム」の+1 。ポリモーフィズムと継承は、無意識のうちに非常に長い間連鎖しており、厳密な静的型付けが唯一の本当の理由でした。
cHao

+1。静的と動的の区別は、単なる注意事項以上のものであると思います-それは、ダックタイピングとJavaスタイルのポリモーフィズムの区別の中心にあります。
サヴァ

8

継承とポリモーフィズムを、オブジェクト間の脆弱な一連の関係に基づいて不必要な制限を課すものとして見始めました。

どうして?

継承(アヒルのタイピングの有無にかかわらず)は、共通の機能の再利用を保証します。一般的な場合、サブクラスで一貫して再利用されることを保証できます。

それで全部です。「不必要な制限」はありません。それは単純化です。

ポリモーフィズムも同様に、「アヒルタイピング」の意味です。同じ方法。インターフェースが同じで実装が異なる多くのクラス。

それは制限ではありません。それは単純化です。


7

継承は悪用されますが、アヒルのタイピングも悪用されます。どちらも問題を引き起こす可能性があり、実際に引き起こします。

強い型付けを使用すると、コンパイル時に多くの「単体テスト」を実行できます。アヒルのタイピングでは、多くの場合、それらを記述する必要があります。


3
テストについてのコメントは、動的なカモタイピングにのみ適用されます。C ++スタイルの静的なカモタイピング(テンプレートを使用)では、コンパイル時にエラーが発生します。(ただし、付与されたC ++テンプレートエラーは解読するのがかなり困難ですが、それはまったく別の問題です。)
Channel72

2

学校で物事を学ぶことの良い点は、あなたそれらを学ぶことです。それほど良くないことは、それらがいつ有用であるか、そしていつ有用でないかを理解せずに、それらを少し独断的に受け入れてもよいということです。

そして、独断的にそれを学んだならば、あなたは後に他の方向に独断的に同じように「反逆」するかもしれません。それも良くありません。

そのようなアイデアと同様に、実用的なアプローチを取るのが最善です。どこに適合し、どこに適合しないかを理解する。そして、売られ過ぎたすべての方法を無視します。


2

はい、静的型付けとインターフェースは制限です。しかし、構造化プログラミングが発明されてからのすべて(つまり、「gotoは有害と見なされる」)は、私たちを制約することに関するものです。ボブおじさんは彼のビデオブログでこれについて素晴らしい見解を持っています。

さて、狭窄は悪いと主張することができますが、一方で、それは非常に複雑なトピックに秩序、制御、親しみをもたらします。

動的型付けや直接メモリアクセスを(再)導入することで制約を緩和すること非常に強力な概念ですが、多くのプログラマーが対処しにくくすることもできます。特にプログラマーは、多くの作業をコンパイラーと型安全性に依存していた。


1

継承は、2つのクラス間の非常に強い関係です。Javaのほうが強いとは思わない。したがって、それを意味する場合にのみ使用する必要があります。パブリック継承は、「通常はis-a」ではなく、「is-a」関係です。継承を使いすぎて混乱してしまうのは本当に簡単です。多くの場合、「has-a」または「takes-functionality-from-a」を表すために継承が使用されます。これは通常、合成によって行われます。

多型は、「is-a」関係の直接的な結果です。DerivedがBaseから継承する場合、すべてのDerived "is-a" Baseであるため、Baseを使用する場所ならどこでもDerivedを使用できます。継承階層でこれが意味をなさない場合、階層が間違っており、おそらくあまりにも多くの継承が行われています。

ダックタイピングは便利な機能ですが、誤用する場合、コンパイラは警告しません。実行時に例外を処理する必要がない場合は、毎回正しい結果が得られていることを確認する必要があります。静的継承階層を定義する方が簡単かもしれません。

私は静的型付けの本当のファンではありません(私はそれを時期尚早な最適化の形であるとしばしば考えます)が、それはいくつかのクラスのエラーを排除します。

静的型付けと定義された継承階層よりも動的型付けとアヒル型付けの方が好きな場合は、それで問題ありません。ただし、Javaの方法には利点があります。


0

C#のクロージャーを使用すればするほど、従来のOOPを実行しなくなることに気付きました。継承は、実装を簡単に共有する唯一の方法であったため、頻繁に使用されすぎて、概念の限界が行き過ぎたと思います。

通常、クロージャーを使用して継承で行うことのほとんどを行うことができます、closureいこともあります。

基本的に、これは仕事に適した状況です。従来の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同じ持っているSoundとしてDog継承に無料で感謝のため。

Haskellのレベルで同じ例を作成して、違いを確認します。

data Animal = Animal | Dog | Cat | GoldenRetriever

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

ここでは、指定したエスケープしていないsoundためGoldenRetriever。一般的に最も簡単なことは

sound GoldenRetriever = sound Dog

ただし、20個の関数がある場合は、単にイメージを作成してください!Haskellのエキスパートがいる場合は、もっと簡単な方法を教えてください。

そうは言っても、パターンマッチングと継承を同時に行うのは素晴らしいことです。現在のインスタンスに実装がない場合、関数はデフォルトで基本クラスになります。


5
私は誓う、AnimalOOPの反チュートリアルです。
テラスティン

@Telastyn YoutubeでDerek Banasから例を学びました。偉大なる師。私の意見では、視覚化と推論は非常に簡単です。より良い例で投稿を自由に編集できます。
クリスチャンガルシア

これは、以前の10の回答に対して実質的な追加はないようです
gnat

@gnat「物質」はすでに存在しますが、主題に慣れていない人は理解しやすい例を見つけるかもしれません。
クリスチャンガルシア14

2
動物や形は、このサイトで吐き気について議論されているいくつかの理由で、多型の本当に悪い応用です。基本的に、猫は「動物」であると言うのは意味がありません。具体的な「動物」がないからです。あなたが本当にモデル化することを意味するのは、アイデンティティではなく行動です。鳴き声または鳴き声として実装できるインターフェイス「CanSpeak」を定義するのは理にかなっています。正方形と円が実装できるインターフェイス「HasArea」を定義するのは理にかなっていますが、一般的なShapeは定義できません。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.