私はポリモーフィズムに関する多くのチュートリアルを読みました、そしてそれらのすべては配列で使用されるポリモーフィズムの例を示しています、例えばあなたはAnimal
(親クラス)配列を持ち、その要素はCat
とDog
とRabbit
(子クラス)、そして配列をループして、各要素でAnimal.speak()
を呼び出します。
(配列以外の)ポリモーフィズムが使用される他の方法はありますか?
ポリモーフィズムのポイントは配列ではありません。それはあなたが話しているオブジェクトの種類を正確に知らなくてもオブジェクトにメッセージを送ることができるということです。
out.print("Hello world");
これは "Hello world"をコンソールに出力するように見えるかもしれませんが、それはわかりません。 out
が何であるかわかりません。これにより、ポップアップダイアログボックスに「Hello world」と表示される場合があります。コンピュータにオーディオスピーカーから「Hello world」と言ってもらえるかもしれません。テキストメッセージを送信する場合があります。知る必要のないコードを見ているのでわかりません。それは多態性です。それが何をするかは、out
が本当に何であるか、そしてそれがどのように構成されたかに依存します。配列は必要ありません。
利点は知識を分離する能力です。コンソールが存在することを認識していないコードは、突然存在しない場合でも変更する必要はありません。変更による影響が小さいため、コードの書き換えが容易になります。 同種配列 トリックは、多くの場合、その能力を自慢するための大雑把な試みです。その結果、これを機能させるには配列が必要だと人々が思っている場合は役に立ちません。
説明を簡単にするためです。
配列、リスト、およびコレクション/コンテナーの他の形式により、異なる機能を持つ異なる要素に対して同じ関数を繰り返し呼び出すことの有用性をすばやく簡単に示すことができます。
ところで、ほとんどの現実のアプリケーションは、Animals
とShapes
のどちらも処理しません。ポリモーフィズムのチュートリアル。
実際には、ポリモーフィズムはコンテナでよく使用されますが、コンテナとは無関係に使用されることもよくあります。
Animal
であることがわかっているのに、なぜDog
オブジェクトを作成するのかすぐに疑問に思うでしょう。コンテナーなしでのポリモーフィズムの実用的な使用のより詳細な例を取得する場合は、いくつかの 一般的なデザインパターン を確認する必要があります。設計。しかし、最初にチュートリアルを終了してください;-)
ポリモーフィズムとは、単一の正式な定義のもとで 複数のフォーム を使用することを意味します。あなたの正式な定義はroleに焦点を当てており、役割を満たす限り、どのフォームもこの定義に属している可能性があります。
オブジェクト指向プログラミングは、rolesがすべてです。ポリモーフィズムとは、役割について話す限り、(可能性としては)複数の、それ以外の場合は明確に異なるオブジェクトによってジョブを実行できるという保証を指します。
ポリモーフィズムの利点は、フォーカスがdetailsからrolesに移動することです。考えてみてください。ドアから出て、まっすぐに車に向かいます。幸いにも、ノートブックには次のメモがあります。
アリゾナ州に住む宗教弁護士のジャックには、2人の子供と1人の妻がいます。彼には2人の兄弟がいます。兄弟は父親と一緒に自動車修理会社を経営しており、幼い頃にガレージで遊んでいました。子供の頃のほとんどをそこで過ごした彼は、実際には経験豊富な自動車整備士です。
ルーシーは情熱的な環境保護主義者であり、ファッションに熱中し、雪と一般的に白いすべてを楽しむ真のスピリチュアルな女性です。彼女は軽い喫煙者です。彼女は過去10年間自動車整備士として認定されており、1か月程度で35歳になります。
ジェイソンはコンピューターエンジニアであり、専門職のピアニストです。彼は人生に対して奇妙な態度を持ち、孤独を楽しんでおり、この世界にいる誰よりもビールを愛しているようです。彼はSunとは仲が良くありませんが、時々買い物に行くことを余儀なくされています。レーサーの息子である彼は、車を中心に成長し、エンジンなどすべてを知っています。
あなたの車が修理を必要とするとき、あなたがジャック、ルーシーとジェイソンについて本当に知っている必要がある唯一の詳細はそれです車を修正します。彼らがこの役割を実行します。したがって、それらはその点でgroupedにすることができます。これが、すべての例がインターフェイスを配列に追加することになる理由です...これは、このグループ化を説明する簡単な方法だからです。重要なのは概念的なグループ化であり、必ずしもmaterialのグループ化ではありませんが、配列はこの側面を強調するのに役立ちます。それがすべてです。
IFixCars carFixer = new Jack();
IFixCars carFixer2 = new Lucy();
IFixCars carFixer3 = new Jason();
...
つまり、ポリモーフィズムは、ある意味では、詳細ではなく役割を利用してニーズを表現する力です。必要ありませんトム弁護士、あなただけ弁護士 。
短い答えとしては、多態性は信じられないほど強力な概念を可能にするということです。サンプルプロジェクトに照らして考えてみましょう。メッセージングライブラリを維持しているとしましょう。意図は、EmailService
、SmsService
などの「SenderServices」があることです。これらはそれぞれ独自のsend(recipient, message)
を実装しています
さて、クライアントを実装する開発者は、これらのそれぞれのクライアントクラスを実装するという考えにあまり興奮していません。したがって、次のヘッダーを持つ抽象メソッドを持つインターフェイスISenderService
を作成することで「サブタイプポリモーフィズム」を利用し、send (recipient, message)
を使用して、両方のサービスをこのインターフェイスの実装にすることができます。
このように、クライアントはEmailSender
やSmsSender
について知る必要はありませんが、使用するISenderService
は渡されます。このようにして、クライアントはメッセージを送信でき、SMSまたはメールを介して、インスタンスを渡した人の意図に従って送信されます。
このアプローチの結果を見てみましょう:
DiscordSender
をクライアントに渡すだけで、すべてが同じように機能します。これは私がこれに触れている他の何かに連れて行きます...
変更ではなく追加によるコーディングこれは本質的には、既存のコードを変更するのではなく、追加することで変更(バグ修正だけでなく)を実行する必要があるという考え方ですインターフェース、そしておそらくそれらを実装する、おそらくあなたの既存の実装を継承することによって。例:
また、写真を送信できる必要があることがわかりました。これで、新しいインターフェイスを作成する必要があります:IPictureSenderService
今、この送信者にはsend_picture(recipient, text, image)
のようなメソッドがありますが、テキストビットのみを処理できるようにして、実装を次のようにすることもできます。 ISenderService
を使用するクライアントによる救済。さて、MmsService
を作成しますが、このクラスはSmsService
を継承します。つまり、このクラスには機能があり、IPictureSenderService
とISenderService
の両方になり、どちらかに依存するクライアントが使用できます。
他にも多くの利点がありますが、オブジェクト指向言語を操作するにつれて、それらの多くが明らかになります。
パラメトリックポリモーフィズム
これは少し異なりますが、本質的には、クラスが「ジェネリック」である場合です。これは、C#でList
のようなtype引数を取るクラスをコーディングする場合です
これが行うことは、本質的には、クライアントクラスで、ライブラリクラスが使用するタイプを決定することです。だからキャスティングはありません。
それについてお読みになることをお勧めしますが、本質的には、クラス内のオブジェクトを特定のタイプにしたい場合に使用されます。魚のリスト
ポリモーフィズムは配列と明示的に関連付けられておらず、ユーザーが指定する例はクラスのように見えます。ポリモーフィズムは、オブジェクト指向プログラミングの4つの原則(残りの3つはカプセル化、抽象化、継承)の1つであり、主に最後の1つである継承と関連しています。
OOPの基本を知りたいのであれば、サイモン・アラディスがホストするビデオシリーズ(私が知っているもの)よりも優れたものはありません。
これは良いスタートです: コンピュータプログラミング:オブジェクト指向言語とは何ですか?
私はポリモーフィズムに関する多くのチュートリアルを読みました、そしてそれらのすべては配列で使用されるポリモーフィズムの例を示しています
配列自体は実際のポイントではありません。重要な点は、ポリモーフィズムによって異なるオブジェクトを同じように扱うことができることです。配列はその簡単で明白な例を提供するだけです。たとえば、要素をループする場合、ループ本体のコードはすべてのオブジェクトで同じです。これは、そのコードがオブジェクトの実際のタイプを認識していないために可能です。インターフェース1 of Animal
あなたはそれをそのような方法で書くことができます知る必要はありません。必要なのは、すべてのオブジェクトがspeak()
をサポートしていることだけです。
私があなたが本当に求めているのは、そのような状況が発生する状況(配列以外)にあると思います。経験を重ねるにつれて、ポリモーフィズムを使用する機会が増えます。
十分に一般的なシナリオの1つは、全体的なロジックは同じであるが特定の詳細が異なる2つの機能をサポートする必要がある場合です。たとえば、一部のデータを処理し、結果をいくつかの異なるファイル形式(JSONやXMLなど)で保存することができます。原則として、コードを2回書くこともできますが、結果として多くの繰り返しが発生することになり、何かを変更する必要がある場合は、2か所で行うことを忘れないでください。また、コードを少しクリーンアップして、結果を保存する部分を、いずれかの形式で保存するifステートメントに分離することもできます。たぶん、あなたのメソッドはある種のフラグまたは列挙を受け入れ、ifステートメントはそれに基づいて何をすべきかを決定します:
ProcessDataAndExport(ExportFormats.JSON)
ただし、別の形式をサポートする必要がある場合は、コードを再度編集する必要があります。そして、もしあなたが別のものを必要とするならば。そして何度も何度も。
代わりに、保存ロジックを別のクラスに分離できます。ポリモーフィズムを使用するには、抽象化を思いつきます-抽象インターフェースを提供する基本クラス、たとえばExporter
です。1ProcessDataAndExport
が使用します。ところで、「抽象化する」とは、エクスポートプロセスの具体的な詳細を知る必要のない方法でProcessDataAndExport
のコードを記述できるタイプを定義することを意味します(それは最初の試みでこれを正しく行わないことは珍しいことではありません。新しい形式のサポートを追加するときにExporterクラスを改良する必要があるかもしれませんが、それらの3または4の後、かなり安定するはずです)。
_class Exporter {
public abstract Export(MyDataType myData, string outputPath);
}
_
そして、代わりにエクスポーターを受け入れるようにProcessDataAndExportを変更できます。
_public ProcessDataAndExport(Exporter exporter) {
// process data (omitted)...
// no if anymore; the choice is not made in this method
exporter.Export(data, outputPath);
}
_
その後、次のように呼び出すことができます。
_Exporter jsonExporter = new JsonExporter();
ProcessDataAndExport(jsonExporter);
_
またはこのように:
_Exporter xmlExporter = new XmlExporter();
ProcessDataAndExport(xmlExporter);
_
またはこのように:
_Exporter userSelectedExporer = ShowSelectExportFormatDialog();
ProcessDataAndExport(userSelectedExporer);
_
新しいフォーマットをサポートするには、エクスポーターから派生する別のクラスを定義するだけです。
しかし、待ってください、まだまだあります!ここに興味深いものがあります。具体的なエクスポーターは、ProcessDataAndExport
関数と同じライブラリー(たとえば、同じEXE、DLL、JARなど)で定義する必要はありません。それらは、アプリケーションがロードしたプラグイン(またはいくつかのプラグイン)から来る可能性があります。 ProcessDataAndExport
はフォーマットをハードコーディングしないため、どのフォーマットでも機能します。
The ShowSelectExportFormatDialog
には、プラグインからのすべてのエクスポーターを検出してそれらのインスタンスを作成する方法がありますが、ProcessDataAndExport
のコードはそのことを心配する必要はありません-すべてのエクスポーターを処理できます同じ方法で、配列の例のループ本体と同じように。
これにより、ProcessDataAndExport
が含まれているライブラリを再コンパイル/再デプロイすることなく、新しい種類のエクスポーターのサポートを追加できます(たとえば、ユーザーは新しいプラグインをダウンロードするだけで済みます)。
さて、一部の言語では、エクスポーターの代わりに、Serializer
と呼ばれるものを使用することができます。また、基本的にはデータ/オブジェクトをいくつかの出力形式に変換します。動作が少し異なるだけです(たとえば、リフレクションを使用してオブジェクトに定義されているプロパティを検出し、それらを自動的に書き出す場合があります)。
しかし、上記とほぼ同じように、派生クラスを作成することにより、独自のカスタム型に対して独自のシリアライザを作成できます。そして、派生元の基本型が、言語に付属する別のライブラリで定義されていても、それは可能です。さらに、シリアライザを受け入れる操作を提供するサードパーティのフレームワークを使用しているとします。独自のカスタムシリアライザーを簡単に渡すことができ、多態的に呼び出されます。そのフレームワークの開発者は、どんな種類のフォーマットが必要かを予測できなかったでしょう。実際、彼らはおそらくあなたのクラスについて決して知らないでしょう。それでも、彼らのコードはあなたのコードで動作します-ループ本体と同じように、既知および未知のすべてのシリアライザを同じ方法で処理できるように記述されているため、同じように配列の例では。
これはあなたがよく知っているかもしれない例です。以前にGUIフレームワークを使用したことがあるかもしれません。多くの場合、フレームワークで定義された基本クラス(MyWindow
、MyForm
)から独自のWindow
またはForm
を派生させます。そのフレームワークは、ウィンドウクラスの詳細について何も知りません。ただし、継承されたメソッドをオーバーライドすると、フレームワークはそれを多態的に呼び出すだけで、バージョンが実行されます。
ポリモーフィズムのもう1つの一般的な用途は、テストのためにオブジェクトを分離することです。たとえば、あるクラスのテストを作成し、その特定の側面のみに焦点を当てたい場合、実際のオブジェクトではなく、偽のオブジェクト(何も実行しないか、またはテスト固有のもの)を渡すことができます。生産で使用します。または、クラスがテストシナリオで不便なもの(ネットワークサービスやデータベースなど)を呼び出す場合、ポリモーフィズムを使用してそれを防ぎ、偽のデータを返すことができます。
このような例はいろいろあります。一部のインターフェイスタイプを受け入れるライブラリ関数は、ポリモーフィズムを使用します。ラムダを受け入れるライブラリ関数も、ほとんど同じ方法でポリモーフィズムを使用します(それらのコードは、受け入れる具体的な関数の詳細を知りません)。別の答えが述べたように、多くの設計パターンは、多目的性と構成の組み合わせに依存して、設計目標を達成します。
追伸また、ある日、さまざまなデータ処理アルゴリズムをサポートする必要があることが判明した場合、代わりに次のような結果になる可能性があります。
ProcessDataAndExport(dataProcessor, exporter);
ここでProcessDataAndExport
は、2つのオブジェクトを多態的に呼び出し、フロー全体を調整します。具象DataProcessor
は特定の処理アルゴリズムに関係しており、結果を返します。その結果、ProcessDataAndExport
が具象Exporter
に渡され、それが出力の生成を担当します。
1 「インターフェース」とは、基本タイプのパブリックメソッド/プロパティのセットを意味します(これはWordの一般的な意味です)。 C#やJavaなどの言語でinterface
キーワードによって宣言されたものを特に意味しているわけではありません(ただし、この意味でもインターフェイスを定義しています)。