ポリモーフィズムをわかりやすく説明するにはどうすればよいでしょうか。
型ポリモーフィズムのように、インターネットや本で主題に関する多くの情報を見つけることができます。しかし、できる限りシンプルにするようにしましょう。
ポリモーフィズムをわかりやすく説明するにはどうすればよいでしょうか。
型ポリモーフィズムのように、インターネットや本で主題に関する多くの情報を見つけることができます。しかし、できる限りシンプルにするようにしましょう。
2 つのオブジェクトが同じメッセージに異なる動作で応答します。送信者は気にする必要はありません。
シンプルなポップリッド付きの缶はすべて同じように開きます。
人間として、あなたはあなたがそのようなものを見つけることができるものなら何でもOpen()できることを知っています。
開いたときに、すべての缶が同じように動作するわけではありません。
ナッツが入っているものもあれば、飛び出す偽のヘビが入っているものもあります。
結果は、缶が「CanOfNuts」または「CanOfSnakes」であるかどうかによって缶のタイプによって異なりますが、これはどのように開くかには関係ありません。缶を開けることができることを知っているだけで、開いた缶の種類に基づいて決定されるある種の結果が得られます。
pUnlabledCan-> Open(); //ナッツを与える可能性があり、ヘビを与える可能性があります。それを呼ぶまでわからない
Open()は「Contents」のジェネリックリターンタイプを持っているので(またはリターンタイプを決定しないかもしれません)、openは常に同じ関数シグネチャを持っています。
人間であるあなたは、ユーザー/発信者です。
Open()は仮想/ポリモーフィック関数です。
「Can」は抽象基本クラスです。
CanOfNutsとCanOfSnakesは、「Can」クラスの多型の子です。
すべての缶を開けることができますが、具体的に何をするのか、どのような内容を返すのかは、缶の種類によって決まります。
pUnlabledCanを見たときに知っているのは、Open()を実行すると、内容が返されるということだけです。その他の動作(顔にヘビをはじくなど)は、特定の缶によって決定されます。
これは、同様の質問からの私の回答からのものです。疑似 C#/Java でのポリモーフィズムの例を次に示します。
class Animal
{
abstract string MakeNoise ();
}
class Cat : Animal {
string MakeNoise () {
return "Meow";
}
}
class Dog : Animal {
string MakeNoise () {
return "Bark";
}
}
Main () {
Animal animal = Zoo.GetAnimal ();
Console.WriteLine (animal.MakeNoise ());
}
Main() メソッドは動物の種類を認識せず、MakeNoise() メソッドの特定の実装の動作に依存します。
ポリモーフィズムの最も簡単な説明は、if/switch ステートメントを削減する方法であるということです。
また、既存のクラスを変更せずに、if/switch ステートメント (または他の人のステートメント) を拡張できるという利点もあります。
たとえばStream
、.NET のクラスを考えてみましょう。ポリモーフィズムがなければ、各メソッドが次のような switch ステートメントを実装する単一の大規模なクラスになります。
public class Stream
{
public int Read(byte[] buffer, int offset, int count)
{
if (this.mode == "file")
{
// behave like a file stream
}
else if (this.mode == "network")
{
// behave like a network stream
}
else // etc.
}
}
FileStream
代わりに、具象型 ( , NetworkStream
) に基づいて実装を自動的に選択することにより、ランタイムがより効率的な方法で切り替えを行えるようにします。
public class FileStream : Stream
{
public override int Read(byte[] buffer, int offset, int count)
{
// behave like a file stream
}
}
public class NetworkStream : Stream
{
public override int Read(byte[] buffer, int offset, int count)
{
// behave like a network stream
}
}
ポリ:多くの
射:形/形
リンゴとオレンジはどちらも果物です。果物は食べられます。したがって、リンゴとオレンジの両方を食べることができます。
キッカー?食べ方が違う!みかんの皮はむくが、りんごの皮はむかない。
したがって、実装は異なりますが、最終結果は同じです。果物を食べます。
アクター vs. キャラクター (または役割)
アヒルのように歩き、アヒルのように鳴くなら、アヒルが必要な場所ならどこでもアヒルとして扱うことができます。
ポリモーフィズムにより、オブジェクトは同じように「見える」ようになりますが、異なる方法で動作します。通常の例は、動物の基本クラスを Speak() メソッドで取得することです。犬のサブクラスは樹皮を発し、豚のサブクラスは鳴き声を発します。
他の開発者が理解できるように、ほとんどの人が使用する 5 秒の短い回答
同じ構文、異なるセマンティクス。
それを説明する最も簡単な方法:複数の種類のオブジェクトに適用できる動詞。
ヒレルが言ったように、他のすべては単なる解説です。
ポリモーフィズムは、共通のプロパティに基づいて世界をボックスに分割し、これらの共通のプロパティのみを使用する場合は、特定のボックス内のアイテムを交換可能として扱います。
ポリモーフィズムとは、異なるものを同じものであるかのように扱う機能であり、それらの間で共有IDを確立し、それを利用します。
ポリモーフィズムは、高レベルのアルゴリズムコードが複数のタイプのデータに対して変更されずに動作できるようにする言語機能です。
これは、操作が各データ型に適切な実装を呼び出すようにすることで実行されます。OOPコンテキスト(この質問のタグによる)でも、この「正しい実装」はコンパイル時または実行時に解決される可能性があります(言語が両方をサポートしている場合)。C ++などの一部の言語では、コンパイラが提供する実行時ポリモーフィズム(つまり、仮想ディスパッチ)のサポートはOOPに固有ですが、他のタイプのポリモーフィズムは、オブジェクトではないデータ型(つまりstruct
、class
インスタンスではないが、int
またはのような組み込み型double
。
(C ++がサポートするポリモーフィズムのタイプは、私の答えにリストされて対比されています:C ++のポリモーフィズム-他の言語をプログラムする場合でも、それは潜在的に有益です)
ポリモーフィズムとは、共通の「親」の知識に依存することで物事を抽象的に扱うことです (動物のような階層を犬と猫の親として考えてください)。
たとえば、すべての動物は酸素を呼吸することができます。それぞれの方法は異なりますが、犬と猫の両方をサポートして、動物が呼吸するための酸素を提供する施設を設計できます。
少しおまけとして、Animal が「抽象的な」識別子であっても、これを行うことができます (本当の「動物」というものはなく、動物の種類だけです)。
ポリモーフィズムとは、1 つの型の場所に複数の型の値を格納することです。
私の執筆時点で、この質問に対する他の回答のほとんどは、実際にはポリモーフィズムではなく動的ディスパッチについて説明していることに注意してください。
動的ディスパッチにはポリモーフィズムが必要ですが、その逆は当てはまりません。Java や C# に非常に似ているが、System.Object にメンバがない言語を想像することができます。値を使用して何かを行う前に、型キャストが必要になります。この概念的な言語では、ポリモーフィズムがありますが、必ずしも仮想メソッドやその他の動的ディスパッチ メカニズムがあるとは限りません。
動的ディスパッチは、関連しているが明確な概念であり、他のほとんどの回答で十分に説明されています。ただし、オブジェクト指向言語で通常動作する方法 (最初の ('this' または 'Self') 引数の型に基づいて関数を選択する) だけが機能する方法ではありません。複数のディスパッチも可能で、すべての引数の型にわたって選択が適用されます。
同様に、オーバーロードの解決と複数のディスパッチは、互いにまったく類似しています。オーバーロードの解決は、静的な型に適用される複数のディスパッチであり、複数のディスパッチは、ポリモーフィックな場所に格納されているランタイム型に適用されるオーバーロードの解決です。
ポリモーフィズムは、同じメソッドが複数のクラスに適用されるときに得られるものです。たとえば、String と List の両方に「Reverse」メソッドがある場合があります。どちらのメソッドも同じ名前 ("Reverse") です。どちらの方法も非常によく似た処理を行います (すべての文字を逆にするか、リスト内の要素の順序を逆にします)。ただし、各「リバース」メソッドの実装は異なり、そのクラスに固有です。(つまり、文字列は文字列のように反転し、リストはリストのように反転します。)
比喩的に言えば、フランス人シェフや日本人シェフに「夕食を作って」と言うことができます。それぞれが独自の方法で「夕食を作る」を実行します。
実際の結果として、オブジェクトを受け入れてその上で「リバース」を呼び出す「リバース エンジン」を作成できます。オブジェクトに Reverse メソッドがある限り、リバース エンジンは機能します。
シェフのアナロジーを拡張するために、シェフに「夕食を作る」ように指示する「ウェイターボット」を作成できます。ウェイターボットは、どんな種類のディナーが作られるかを知る必要はありません。シェフと話していることを確認する必要さえありません。重要なのは、「シェフ」(または消防士、自動販売機、ペットフード ディスペンサー)が「夕食を作る」ように言われたときに何をすべきかを知っていることだけです。
これにより、プログラマーとして得られるのは、コードの行数が少なくなり、タイプ セーフまたはレイト バインディングのいずれかになります。たとえば、タイプ セーフと早期バインディングの例を次に示します (C に似た言語で作成しています)。
class BankAccount {
void SubtractMonthlyFee
}
class CheckingAccount : BankAccount {}
class SavingsAccount : BankAccount {}
AssessFee(BankAccount acct) {
// This will work for any class derived from
// BankAccount; even classes that don't exist yet
acct.SubtractMonthlyFee
}
main() {
CheckingAccount chkAcct;
SavingsAccount saveAcct;
// both lines will compile, because both accounts
// derive from "BankAccount". If you try to pass in
// an object that doesn't, it won't compile, EVEN
// if the object has a "SubtractMonthlyFee" method.
AssessFee(chkAcct);
AssessFee(saveAcct);
}
タイプ セーフではなく、レイト バインディングを使用する例を次に示します。
class DatabaseConnection {
void ReleaseResources
}
class FileHandle {
void ReleaseResources
}
FreeMemory(Object obj) {
// This will work for any class that has a
// "ReleaseResources" method (assuming all
// classes are ultimately derived from Object.
obj.ReleaseResources
}
main() {
DatabaseConnection dbConn;
FileHandle fh;
// You can pass in anything at all and it will
// compile just fine. But if you pass in an
// object that doesn't have a "ReleaseResources"
// method you'll get a run-time error.
FreeMemory(dbConn);
FreeMemory(fh);
FreeMemory(acct); //FAIL! (but not until run-time)
}
優れた例として、.NET の ToString() メソッドを見てください。すべてのクラスは Object クラスから派生しているため、すべてのクラスがそれを持っています。ただし、各クラスは、それ自体に意味のある方法で ToString() を実装できます。
編集: シンプル != ショート、私見
ポリモーフィズムという用語は、オーバーロード関数にも適用できます。例えば、
string MyFunc(ClassA anA);
string MyFunc(ClassB aB);
ポリモーフィズムの非オブジェクト指向の例です。
オブジェクトが同じメッセージにさまざまな方法で応答する必要がある機能です。
たとえば、smalltalk、Ruby、Objective-Cなどの言語では、メッセージを送信するだけで応答します。
dao = XmlDao.createNewInstance() #obj 1
dao.save( data )
dao = RdbDao.createNewnewInstance() #obj 2
dao.save( data )
この例では、2つの異なるオブジェクトが、同じメッセージに対して異なる方法で応答しました:「createNewInstance()およびsave(obj)」
彼らは同じメッセージに対して、異なる方法で行動します。上記の言語では、クラスが同じクラス階層にない場合もあります。メッセージに応答するだけで十分です。
Java、C ++、C#などの言語では、オブジェクトをオブジェクト参照に割り当てるには、インターフェイスを実装するか、共通クラスのサブクラスにすることで、同じ型階層を共有する必要があります。
簡単..そしてシンプル。
ポリモーフィズムは、オブジェクト指向プログラミングの最も重要で関連性のある機能です。
あるタイプのオブジェクト(たとえば、車)が別のタイプ(たとえば、車両)のように動作(たとえば、ブレーキ)する能力。これは、通常、タイプ階層の1つのポイントで共通の祖先(たとえば、車は車両のサブタイプ)を示唆します。 。
これは、新しいコードを呼び出すために古い風邪をひく方法にすぎません。他の人が実装しなければならないメソッド(例-getArea)を使用して、いくつかの「Shape」インターフェースを受け入れるアプリケーションを作成します。誰かがそのインターフェースを実装するための新しいwhiz-bangの方法を思いついた場合、古いコードはgetAreaメソッドを介してその新しいコードを呼び出すことができます。
これは、似たようなことを同じように行うことができるさまざまなものを、その方法を気にせずに扱う方法です。
車、トラック、スケートボード、飛行機など、さまざまな種類の乗り物が走り回るゲームがあるとします。それらはすべて停止できますが、それぞれの乗り物は異なる方法で停止します。一部の車両はギアをシフトダウンする必要がある場合があり、一部の車両は冷間停止できる場合があります。ポリモフィズムにより、これが可能になります
foreach (Vehicle v in Game.Vehicles)
{
v.Stop();
}
stop が実装される方法は、さまざまな Vehicles に延期されるため、プログラムはそれを気にする必要はありません。
私が試して考える方法は、同じように見えますが、インスタンスに応じて異なる機能を持つことができるものです. だからあなたはタイプを持つことができます
interface IJobLoader
見た目は同じでも使い方次第で様々な機能を発揮します。BatchJobLoader、NightlyJobLoader などのインスタンスがある場合があります
多分私は道を外れています。
ポリモーフィズムは、関数を別の関数に渡す問題に対するオブジェクト指向のソリューションです。Cでできること
void h() { float x=3.0; printf("%f", x); }
void k() { int y=5; printf("%i", y); }
void g(void (*f)()) { f(); }
g(h); // output 3.0
g(k); // output 5
C では、関数が追加のパラメーターに依存する場合、事態は複雑になります。関数 h と k が異なるタイプのパラメーターに依存している場合、問題が発生し、キャストを使用する必要があります。これらのパラメーターをデータ構造に格納し、そのデータ構造へのポインターを g に渡し、それを h または k に渡す必要があります。h と k は、ポインターを適切な構造体へのポインターにキャストし、データをアンパックします。キャストエラーの可能性があるため、非常に面倒で非常に安全ではありません:
void h(void *a) { float* x=(float*)a; printf("%f",*x); }
void k(void *a) { int* y=(int*)a; printf("%i",*y); }
void g(void (*f)(void *a),void *a) { f(a); }
float x=3.0;
int y=5;
g(h,&x); // output x
g(k,&y); // output y
そこで彼らはポリモーフィズムを発明しました。h と k はクラスに昇格され、実際の関数はメソッドに昇格されます。パラメーターは、それぞれのクラス h または k のメンバー変数です。関数を渡す代わりに、必要な関数を含むクラスのインスタンスを渡します。インスタンスには独自のパラメーターが含まれています。
class Base { virtual public void call()=0; }
class H : public Base { float x; public void call() { printf("%f",x);} } h;
class K : public Base { int y; public void call() { printf("%i",y);} } k;
void g(Base &f) { f.call(); };
h.x=3.0;
k.y=5;
g(h); // output h.x
g(k); // output k.x