web-dev-qa-db-fra.com

Comment le polymorphisme est-il utilisé dans le monde réel?

J'essaie de comprendre comment le polymorphisme est utilisé dans un projet réel, mais je ne peux trouver que l'exemple classique (ou quelque chose de similaire) d'avoir une classe parent Animal avec une méthodespeak() et de nombreuses classes enfants qui remplacent cette méthode, et maintenant vous pouvez appeler la méthode speak() sur n'importe quel objet enfant, par exemple:

Animal animal;

animal = dog;
animal.speak();

animal = cat;
animal.speak();
17
Christopher

Stream est un excellent exemple de polymorphisme.

Le flux représente une "séquence d'octets qui peut être lue ou écrite". Mais cette séquence peut provenir de fichiers, de mémoire ou de nombreux types de connexions réseau. Ou il peut servir de décorateur, qui enveloppe le flux existant et transforme les octets d'une manière ou d'une autre, comme le chiffrement ou la compression.

De cette façon, le client qui utilise Stream n'a pas besoin de se soucier de la provenance des octets. Juste qu'ils peuvent être lus en séquence.

Certains diraient que Stream est un mauvais exemple de polymorphisme, car il définit de nombreuses "fonctionnalités" que ses implémenteurs ne prennent pas en charge, comme le flux réseau permettant uniquement la lecture ou l'écriture, mais pas les deux en même temps. Ou manque de recherche. Mais ce n'est qu'une question de complexité, car Stream peut être subdivisé en plusieurs parties qui pourraient être implémentées indépendamment.

35
Euphoric

Un exemple typique lié aux jeux serait une classe de base Entity, fournissant des membres communs tels que draw() ou update().

Pour un exemple plus pur orienté données, il pourrait y avoir une classe de base Serializable fournissant une saveToStream() et loadFromStream() commune.

7
Mario

Vous voyez beaucoup d'héritage et de polymorphisme dans la plupart des kits d'outils d'interface utilisateur.

Par exemple, dans la boîte à outils JavaFX UI, Button hérite de ButtonBase qui hérite de Labeled qui hérite de Control qui hérite de Region qui hérite de Parent qui hérite de Node qui hérite de Object. De nombreuses couches remplacent certaines méthodes des précédentes.

Lorsque vous souhaitez que ce bouton apparaisse à l'écran, vous l'ajoutez à un Pane, qui peut accepter tout ce qui hérite de Node en tant qu'enfant. Mais comment un volet sait-il quoi faire avec un bouton lorsqu'il le voit simplement comme un objet générique Node? Cet objet peut être n'importe quoi. Le volet peut le faire parce que le bouton redéfinit les méthodes de Node avec n'importe quelle logique spécifique au bouton. Le volet appelle simplement les méthodes définies dans Node et laisse le reste à l'objet lui-même. Ceci est un exemple parfait de polymorphisme appliqué.

Les boîtes à outils d'interface utilisateur ont une très grande signification dans le monde réel, ce qui les rend utiles pour enseigner à la fois pour des raisons académiques et pratiques.

Cependant, les boîtes à outils de l'interface utilisateur présentent également un inconvénient important: elles ont tendance à être énormes . Lorsqu'un ingénieur logiciel néophyte essaie de comprendre le fonctionnement interne d'un cadre d'interface utilisateur commun, il rencontrera souvent plus d'une centaine de classes , la plupart d'entre elles servant très ésotérique fins. "Qu'est-ce que c'est que ReadOnlyJavaBeanLongPropertyBuilder ? Est-ce important? Dois-je comprendre ce que c'est bon pour?" Les débutants peuvent facilement se perdre dans ce terrier de lapin. Ils peuvent donc fuir de terreur ou rester à la surface où ils apprennent simplement la syntaxe et essaient de ne pas trop réfléchir à ce qui se passe réellement sous le capot.

6
Philipp

Il existe différents types de polymorphisme, celui qui nous intéresse est généralement le polymorphisme d'exécution/répartition dynamique.

Une description de très haut niveau du polymorphisme d'exécution est qu'un appel de méthode fait des choses différentes selon le type d'exécution de ses arguments: l'objet lui-même est responsable de la résolution d'un appel de méthode. Cela permet une grande flexibilité.

L'une des façons les plus courantes d'utiliser cette flexibilité est pour injection de dépendance, par ex. afin que je puisse basculer entre différentes implémentations ou injecter des objets fictifs pour les tests. Si je sais à l'avance qu'il n'y aura qu'un nombre limité de choix possibles, je pourrais essayer de les coder en dur avec des conditions, par exemple:

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

Cela rend le code difficile à suivre. L'alternative est d'introduire une interface pour cette foo-opération et d'écrire une implémentation normale et une implémentation simulée de cette interface, et "d'injecter" à l'implémentation souhaitée au moment de l'exécution. "L'injection de dépendance" est un terme compliqué pour "passer le bon objet comme argument".

À titre d'exemple concret, je travaille actuellement sur un genre de problème d'apprentissage automatique. J'ai un algorithme qui nécessite un modèle de prédiction. Mais je veux essayer différents algorithmes d'apprentissage automatique. J'ai donc défini une interface. De quoi ai-je besoin de mon modèle de prédiction? Étant donné un échantillon d'entrée, la prédiction et ses erreurs:

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

Mon algorithme prend une fonction d'usine qui forme un modèle:

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

J'ai maintenant différentes implémentations de l'interface du modèle et je peux les comparer les unes aux autres. L'une de ces implémentations prend en fait deux autres modèles et les combine en un modèle boosté. Merci donc à cette interface:

  • mon algorithme n'a pas besoin de connaître à l'avance des modèles spécifiques,
  • Je peux facilement échanger des modèles et
  • J'ai beaucoup de flexibilité pour implémenter mes modèles.

Un cas d'utilisation classique du polymorphisme est dans les interfaces graphiques. Dans un framework GUI comme Java AWT/Swing /… il existe différents composants . L'interface composant/classe de base décrit des actions telles que se peindre à l'écran ou réagir aux clics de souris. De nombreux composants sont des conteneurs qui gèrent des sous-composants. Comment un tel conteneur peut-il se dessiner?

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

Ici, le conteneur n'a pas besoin de connaître à l'avance les types exacts des sous-composants - tant qu'ils sont conformes à l'interface Component, le conteneur peut simplement appeler la méthode polymorphe Paint(). Cela me donne la liberté d'étendre la hiérarchie des classes AWT avec de nouveaux composants arbitraires.

Il existe de nombreux problèmes récurrents tout au long du développement de logiciels qui peuvent être résolus en appliquant le polymorphisme comme technique. Ces paires récurrentes de problèmes et de solutions sont appelées modèles de conception , et certaines d'entre elles sont rassemblées dans le livre du même nom. Dans les termes de ce livre, mon modèle d'apprentissage automatique injecté serait une stratégie que j'utilise pour "définir une famille d'algorithmes, encapsuler chacun d'eux, et les rendre interchangeables ". L'exemple Java-AWT où un composant peut contenir des sous-composants est un exemple de composite .

Mais toutes les conceptions n'ont pas besoin d'utiliser le polymorphisme (au-delà de l'activation de l'injection de dépendances pour les tests unitaires, ce qui est un très bon cas d'utilisation). La plupart des problèmes sont par ailleurs très statiques. Par conséquent, les classes et les méthodes ne sont souvent pas utilisées pour le polymorphisme, mais simplement comme des espaces de noms pratiques et pour la jolie syntaxe d'appel de méthode. Par exemple. de nombreux développeurs préfèrent les appels de méthode comme account.getBalance() à un appel de fonction largement équivalent Account_getBalance(account). C'est une approche parfaitement fine, c'est juste que de nombreux appels de "méthode" n'ont rien à voir avec le polymorphisme.

6
amon

Bien qu'il y ait déjà de bons exemples ici, un autre est de remplacer les animaux par des appareils:

  • Device peut être powerOn(), powerOff(), setSleep() et peut getSerialNumber().
  • SensorDevice peut faire tout cela et fournir des fonctions polymorphes telles que getMeasuredDimension(), getMeasure(), alertAt(threashhold) et autoTest().
  • bien entendu, getMeasure() ne sera pas implémentée de la même manière pour un capteur de température, un détecteur de lumière, un détecteur de son ou un capteur volumétrique. Et bien sûr, chacun de ces capteurs plus spécialisés peut avoir des fonctions supplémentaires disponibles.
3
Christophe

La présentation est une application très courante, peut-être la plus courante étant ToString (). C'est essentiellement Animal.Speak (): vous dites à un objet de se manifester.

Plus généralement, vous dites à un objet de "faire sa chose". Pensez à enregistrer, charger, initialiser, éliminer, ProcessData, GetStatus.

2
Martin Maat

Ma première utilisation pratique du polymorphisme a été une implémentation de Heap en Java.

J'avais une classe de base avec implémentation de méthodes insert, removeTop où la différence entre max et min Heap ne serait que la façon dont la comparaison de méthode fonctionne.

abstract class Heap {  

 abstract boolean compare ( int x , int y );

 boolean insert(int x ) { ... }

 int removeTop() { ... }
}

Donc, quand je voulais avoir MaxHeap et MinHeap, je pouvais simplement utiliser l'héritage.

class MaxHeap extends Heap {

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

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

Voici un scénario réel pour le polymorphisme de l'application Web/de la table de base de données :

J'utilise Ruby on Rails pour développer des applications Web, et une chose que beaucoup de mes projets ont en commun est la possibilité de télécharger des fichiers (photos, PDFs Ainsi, par exemple, un User peut avoir plusieurs images de profil, et un Product peut également avoir de nombreuses images de produit. Les deux ont le comportement de télécharger et de stocker des images, ainsi que redimensionnement, génération de vignettes, etc. Afin de rester DRY et partager le comportement de Picture, nous voulons rendre Picture polymorphe afin qu'il puisse appartenir à User et Product.

En Rails je concevoir mes modèles comme tels:

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

et une migration de base de données pour créer la table 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

Les colonnes imageable_id et imageable_type sont utilisés par Rails en interne. Fondamentalement, imageable_type contient le nom de la classe ("User", "Product", etc.) et imageable_id est l'ID de l'enregistrement associé. Donc imageable_type = "User" et imageable_id = 1 serait l'enregistrement dans la table users avec id = 1.

Cela nous permet de faire des choses comme user.pictures pour accéder aux photos de l'utilisateur, ainsi qu'à product.pictures pour obtenir les photos d'un produit. Ensuite, tout le comportement lié à l'image est encapsulé dans la classe Photo (et non dans une classe distincte pour chaque modèle qui a besoin de photos), les choses sont donc conservées au SEC.

Plus de lecture: Rails associations polymorphes .

1
Chris Cirefice

Il existe de nombreux algorithmes de tri disponibles comme le tri à bulles, le tri par insertion, le tri rapide, le tri en tas, etc.

Le client fourni avec l'interface de tri ne concerne que la fourniture d'un tableau en entrée, puis la réception d'un tableau trié. Pendant l'exécution, en fonction de certains facteurs, une mise en œuvre de tri appropriée peut être utilisée. C'est l'un des exemples du monde réel où le polymorphisme est utilisé.

Ce que j'ai décrit ci-dessus est un exemple de polymorphisme d'exécution alors que la surcharge de méthode est un exemple de polymorphisme de temps de compilation où la conformité dépend des types de paramètres i/p et o/p et du nombre de paramètres lie l'appelant à la bonne méthode au moment de la conformité lui-même.

J'espère que cela clarifie.

0
rahulaga_dev