ポリモーフィズムは実際の世界でどのように使用されていますか?[閉まっている]


17

私は実際のプロジェクトで多態性がどのように使用されているかを理解しようとしていますAnimalが、method を持つ親クラスと、speak()このメソッドをオーバーライドする多くの子クラスを持つ古典的な例(またはそれに似たもの)しか見つけることができません次のように、speak()任意の子オブジェクトでメソッドを呼び出すことができます。

Animal animal;

animal = dog;
animal.speak();

animal = cat;
animal.speak();



1
毎日見たり使ったりしているコレクション自体は、ポリモーフィズムとは何かを理解するのに十分です。しかし、問題解決においてポリモーフィズムを効果的に使用する方法は、単に議論するだけでなく、経験によって最も得られるスキルです。続けて手を汚してください。
ドゥルガダスS

何らかの最小限のインターフェイス(描画する必要があるオブジェクトのセットなど)をすべてサポートするタイプのセットがある場合、通常、インターフェイスは、描画の呼び出しからオブジェクト間の違いを隠すのに適しています。また、基本オブジェクトとそれを継承するかなりの数の型をほぼ同じ方法で処理できるメソッドを持つAPIを作成(または使用)している場合、ポリモーフィズムが、それらのタイプ。
jrh

一般に、さまざまな型を処理するためにオーバーロードされたメソッドを頻繁に作成し、コードが似ている場合、またはif(x is SomeType) DoSomething()頻繁に記述している場合、ポリモーフィズムを使用する価値があります。私にとってポリモーフィズムは、別のメソッドを作成するときと同様の決定です。コードを数回繰り返したことがわかった場合、通常それをメソッドにリファクタリングし、if object is this type do thisコードを頻繁に作成していることがわかった場合、リファクタリングして、インターフェイスまたはクラスを追加する価値があります。
jrh

回答:


35

ストリームはポリモーフィズムの好例です。

ストリームは、「読み取りまたは書き込み可能なバイトのシーケンス」を表します。ただし、このシーケンスは、ファイル、メモリ、またはさまざまな種類のネットワーク接続から取得できます。または、既存のストリームをラップし、暗号化や圧縮などの何らかの方法でバイトを変換するデコレーターとして機能します。

このように、Streamを使用するクライアントは、バイトの送信元を気にする必要がありません。順番に読むことができるというだけです。

Streamポリモーフィズムの間違った例だと言う人もいます。これは、ネットワークストリームが読み取りまたは書き込みのみを許可し、同時に両方を許可しないなど、実装者がサポートしない多くの「機能」を定義するためです。またはシークの欠如。しかし、それは複雑さの問題に過ぎず、Stream独立して実装できる多くの部分に細分することができます。


2
C ++のような複数の仮想継承を持つ言語では、この例でも、基本ストリームクラスからの入力と出力ストリームクラスを派生し、I / Oストリームを作成するには、両方を拡張することで...「恐ろしいダイヤモンド」パターンを示すことができる
ジャイル

2
@gyreそして、うまくやった、ダイヤモンドパターンを「恐れる」理由はありません。ダイヤモンドの反対側のカウンターパートを認識し、それと名前の競合を引き起こさないことが重要であり、挑戦であり、迷惑であり、実行可能な場合はダイヤモンドのパターンを回避する理由があります...たとえば、命名規則を設定するだけで問題を解決できます。
KRyan

+1 Streamは、私のお気に入りのポリモーフィズムの例です。欠陥のある「動物、哺乳類、犬」モデルを人々に教えることすらしませんStream。しかし、より良い仕事をします。
ファラプ

@KRyan私はそれを「恐ろしいダイヤモンド」と呼ぶことによって自分の考えを表現していませんでした。同意します; それは、すべての開発者が頭を包んで適切に使用できるものでなければならないと思います。
ジャイル

@gyreああ、そう、私は実際にそれを得た。それが、矛盾ではなく、あなたの思考の延長であることを示すために、「そして」から始めた理由です。
KRyan

7

典型的なゲーム関連の例は、基本クラスでEntitydraw()またはなどの一般的なメンバーを提供しupdate()ます。

より純粋なデータ指向の例ではSerializable、共通saveToStream()およびを提供する基本クラスがありloadFromStream()ます。


6

ポリモーフィズムにはさまざまな種類がありますが、通常はランタイムポリモーフィズム/動的ディスパッチが対象です。

ランタイムポリモーフィズムの非常に高レベルの説明は、メソッド呼び出しは引数のランタイムタイプに応じて異なることを行うということです。オブジェクト自体がメソッド呼び出しを解決する責任があります。これにより、非常に大きな柔軟性が得られます。

この柔軟性を使用する最も一般的な方法の1つは依存性注入です。たとえば、異なる実装間で切り替えたり、テスト用にモックオブジェクトを注入したりできます。可能な選択肢が限られていることを事前に知っている場合、条件付きでそれらをハードコーディングすることができます。たとえば:

void foo() {
  if (isTesting) {
    ... // do mock stuff
  } else {
    ... // do normal stuff
  }
}

これにより、コードを追跡しにくくなります。別の方法は、そのfoo操作のインターフェイスを導入し、そのインターフェイスの通常の実装とモック実装を記述し、実行時に目的の実装に「注入」することです。「依存性注入」は、「正しいオブジェクトを引数として渡す」ための複雑な用語です。

実世界の例として、私は現在、親切な機械学習の問題に取り組んでいます。予測モデルを必要とするアルゴリズムがあります。しかし、さまざまな機械学習アルゴリズムを試してみたいと思います。そこで、インターフェイスを定義しました。予測モデルには何が必要ですか?いくつかの入力サンプルが与えられると、予測とそのエラー:

interface Model {
  def predict(sample) -> (prediction: float, std: float);
}

私のアルゴリズムは、モデルをトレーニングするファクトリー関数を使用します。

def my_algorithm(..., train_model: (observations) -> Model, ...) {
  ...
  Model model = train_model(observations);
  ...
  y, std = model.predict(x)
  ...
}

現在、モデルインターフェイスのさまざまな実装があり、それらを相互にベンチマークできます。これらの実装の1つは、実際には他の2つのモデルを取得し、それらを組み合わせてブーストモデルにします。このインターフェースのおかげで:

  • 私のアルゴリズムは特定のモデルについて事前に知る必要はありません。
  • モデルを簡単に交換できます。
  • モデルの実装には非常に柔軟性があります。

ポリモーフィズムの典型的な使用例はGUIです。Java AWT / Swing /などのGUIフレームワークには、さまざまなコンポーネントがあります。コンポーネントインターフェイス/ベースクラスは、画面へのペイントやマウスクリックへの反応などのアクションを記述します。多くのコンポーネントは、サブコンポーネントを管理するコンテナです。このようなコンテナはどのようにそれ自体を描画しますか?

void paint(Graphics g) {
  super.paint(g);
  for (Component child : this.subComponents)
    child.paint(g);
}

ここで、コンテナはサブコンポーネントの正確なタイプを事前に知る必要はありません- Componentインターフェースが準拠している限り、コンテナはポリモーフィックpaint()メソッドを単に呼び出すことができます。これにより、AWTクラス階層を任意の新しいコンポーネントで拡張する自由が与えられます。

ポリモーフィズムを手法として適用することで解決できるソフトウェア開発全体に繰り返し発生する問題が多数あります。これらの繰り返し発生する問題とソリューションのペアはデザインパターンと呼ばれ、それらのいくつかは同じ名前の本に集められています。その本の用語では、私が注入した機械学習モデルは、「アルゴリズムのファミリを定義し、各アルゴリズムをカプセル化し、それらを交換可能にする」ために使用する戦略になります。コンポーネントにサブコンポーネントを含めることができるJava-AWTの例は、コンポジットの例です。

しかし、すべての設計でポリモーフィズムを使用する必要があるわけではありません(ユニットテストで依存性注入を有効にする以外に、これは本当に良いユースケースです)。それ以外の場合、ほとんどの問題は非常に静的です。その結果、クラスとメソッドはポリモーフィズムではなく、単に便利な名前空間として、またメソッド呼び出し構文として使用されます。たとえば、多くの開発者はaccount.getBalance()、ほぼ同等の関数呼び出しよりもメソッド呼び出しを好みますAccount_getBalance(account)。これは完全に素晴らしいアプローチです。多くの「メソッド」呼び出しはポリモーフィズムとは関係ありません。


6

ほとんどのUIツールキットには、多くの継承とポリモーフィズムが見られます。

例えば、JavaFXのUIツールキットは、Buttonから継承ButtonBaseその継承からLabeledから継承Controlから継承Regionれる継承からParentから継承Nodeから継承Object。多くのレイヤーは、前のものからいくつかのメソッドをオーバーライドします。

そのボタンを画面に表示する場合は、Paneに追加します。このボタンNodeは、子として継承したものをすべて受け入れることができます。しかし、Paneは、ボタンを汎用Nodeオブジェクトとして認識しただけで、ボタンの処理方法をどのように知るのでしょうか?そのオブジェクトは何でも構いません。ButtonがNodeのメソッドをボタン固有のロジックで再定義するため、ペインはそれを実行できます。ペインは、Nodeで定義されたメソッドを呼び出すだけで、残りはオブジェクト自体に残されます。これは、適用された多型の完璧な例です。

UIツールキットは実世界で非常に重要であるため、学術的および実用的な理由から教えるのに役立ちます。

しかし、UIツールキットはまた、重大な欠点を持っている:彼らはする傾向がある巨大な。初心者のソフトウェアエンジニアが共通のUIフレームワークの内部動作を理解しようとすると、100を超えるクラスに遭遇することが多く、そのほとんどは非常に難解な目的に使用されます。「一体何ReadOnlyJavaBeanLongPropertyBuilderなのか?それは重要なのか?それが何のために良いのか理解しなければならないのか?」初心者はそのウサギの穴で簡単に迷子になります。そのため、彼らは恐怖から逃げるか、構文を習得するだけの表面に留まり、実際に何が起こっているのかを考えすぎないようにするかもしれません。


3

ここにはすでに素晴らしい例がありますが、もう1つは動物をデバイスに置き換えることです。

  • DeviceすることができpowerOn()powerOff()setSleep()とすることができますgetSerialNumber()
  • SensorDeviceこのすべてを行う、とのような多型の機能を提供することができgetMeasuredDimension()getMeasure()alertAt(threashhold)autoTest()
  • もちろん、getMeasure()温度センサー、光検出器、音検出器、または体積センサーに対して同じ方法で実装されることはありません。そしてもちろん、これらのより特殊化されたセンサーにはそれぞれ、いくつかの追加機能が利用可能です。

2

プレゼンテーションは非常に一般的なアプリケーションであり、おそらく最も一般的なアプリケーションはToString()です。これは基本的にAnimal.Speak()です:オブジェクトに自分自身を明示するように指示します。

より一般的には、オブジェクトに「そのことをする」ように指示します。保存、ロード、初期化、破棄、プロセスデータ、GetStatusを考えてください。


2

多態性の私の最初の実用的な使用法は、Javaでのヒープの実装でした。

メソッドインサート、removeTopの実装を備えた基本クラスがあり、最大ヒープと最小ヒープの違いはメソッド比較のしくみのみです。

abstract class Heap {  

 abstract boolean compare ( int x , int y );

 boolean insert(int x ) { ... }

 int removeTop() { ... }
}

したがって、MaxHeapとMinHeapが必要な場合は、継承を使用できます。

class MaxHeap extends Heap {

   MaxHeap(int maxSize) {super(maxSize);}

   @Override
   boolean compare(int x, int y) {
       return x>y; // x<y for minHeap
   }
}

1

Webアプリ/データベーステーブルのポリモーフィズムの実際のシナリオは次のとおりです

Ruby on Railsを使用してWebアプリを開発していますが、多くのプロジェクトで共通していることの1つは、ファイル(写真、PDFなど)をアップロードできることです。そのため、たとえば、User複数のプロフィール写真があり、Product多数の製品画像がある場合もあります。両方とも、画像のアップロードと保存、およびサイズ変更、サムネイルの生成などの動作があります。DRYを維持し、の動作を共有するためにPicturePicture両方Userとに属することができるようにポリモーフィックを作成しProductます。

Railsでは、モデルを次のように設計します。

class Picture < ApplicationRecord
  belongs_to :imageable, polymorphic: true
end

class User < ApplicationRecord
  has_many :pictures, as: :imageable
end

class Product < ApplicationRecord
  has_many :pictures, as: :imageable
end

そして、picturesテーブルを作成するためのデータベースの移行:

class CreatePictures < ActiveRecord::Migration[5.0]
  def change
    create_table :pictures do |t|
      t.string  :name
      t.integer :imageable_id
      t.string  :imageable_type
      t.timestamps
    end

    add_index :pictures, [:imageable_type, :imageable_id]
  end
end

imageable_idimageable_typeは、Railsによって内部的に使用されます。基本的に、imageable_type(クラス名保持する"User""Product"など)、およびimageable_id関連するレコードのIDです。だから、imageable_type = "User"およびimageable_id = 1レコードだろうusersとテーブルid = 1

これによりuser.pictures、ユーザーの写真にアクセスしたりproduct.pictures、製品の写真を取得したりすることができます。その後、画像に関連するすべての動作がPhotoクラスにカプセル化されます(写真を必要とする各モデルの個別のクラスではなく)ので、物事はドライに保たれます。

詳細:Railsポリモーフィックアソシエーション


0

バブルソート、挿入ソート、クイックソート、ヒープソートなどのような多くのソートアルゴリズムがあり、複雑さが異なり、使用するのに最適なアルゴリズムはさまざまな要因によって異なります(例:配列のサイズ)

並べ替えインターフェイスを備えたクライアントは、入力として配列を提供し、並べ替えられた配列を受け取ることのみを考慮します。実行時に特定の要因に応じて、適切なソートの実装を使用できます。これは、ポリモーフィズムが使用される実例の1つです。

上記で説明したのはランタイムポリモーフィズムの例ですが、メソッドのオーバーロードはコンパイル時ポリモーフィズムの例であり、コンパイラはi / pおよびo / pパラメータタイプとパラメータの数に応じて、コンパイル時に呼び出し側を正しいメソッドにバインドします。

これが明らかになることを願っています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.