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


17

J'essaie de comprendre comment le polymorphisme est utilisé dans un projet de la vie réelle, mais je ne peux trouver que l'exemple classique (ou quelque chose de similaire) d'avoir une Animalclasse parent avec une méthode speak(), 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();



1
Les collections, que vous voyez et utilisez chaque jour, suffisent à elles seules pour comprendre ce qu'est le polymorphisme. Mais comment utiliser efficacement le polymorphisme dans la résolution de problèmes est une compétence que vous acquérez principalement par l'expérience et pas seulement en discutant. Allez-y et salissez vos mains.
Durgadass S

Si vous avez un ensemble de types qui prendront tous en charge une sorte d'interface minimale (par exemple, un ensemble d'objets qui doivent être dessinés), une interface est généralement bien adaptée pour masquer les différences entre les objets de l'appel pour le dessiner. De plus, si vous créez (ou travaillez avec) une API qui a des méthodes qui peuvent servir un objet de base et un nombre important de types qui en héritent plus ou moins de la même manière , le polymorphisme peut être le meilleur moyen d'abstraire les différences entre ces types.
2018

En général, si vous créez fréquemment des méthodes surchargées pour gérer différents types et que le code est similaire, ou si vous écrivez if(x is SomeType) DoSomething()souvent, il peut être utile d'utiliser le polymorphisme. Pour moi, le polymorphisme est une décision similaire au moment de faire une méthode distincte, si je trouve que j'ai répété le code plusieurs fois, je le refactorise habituellement en méthode, et si je trouve que je fais du if object is this type do thiscode souvent, cela pourrait être mérite d'être refactorisé et d'ajouter une interface ou une classe.
2018

Réponses:


35

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 diront que Streamc'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 elle Streampeut être subdivisée en plusieurs parties qui pourraient être mises en œuvre indépendamment.


2
Dans les langages avec héritage multiple et virtuel comme C ++, cet exemple peut même démontrer le modèle "diamant redouté" ... en dérivant les classes de flux d'entrée et de sortie d'une classe de flux de base, et en les étendant pour créer un flux d'E / S
gyre

2
@gyre Et bien fait, il n'y a aucune raison de «redouter» le motif diamant. Avoir besoin de connaître l'homologue opposé dans le diamant et ne pas causer de conflits de nom avec lui est important, et un défi, et ennuyeux, et une raison pour éviter le modèle de diamant lorsque cela est possible ... mais n'allez pas trop loin en le redoutant quand avoir simplement, disons, une convention de dénomination pourrait résoudre les problèmes.
KRyan

+1 Streams est mon exemple de polymorphisme préféré de tous les temps. Je n'essaie même plus d'enseigner aux gens le modèle défectueux «animal, mammifère, chien», il Streamfait un meilleur travail.
Pharap

@KRyan Je n'exprimais pas mes propres pensées en l'appelant le "diamant redouté", je viens de l'entendre se référer comme tel. Je suis complètement d'accord; Je pense que c'est quelque chose que chaque développeur devrait être capable de bien comprendre et d'utiliser de manière appropriée.
rôle

@gyre Oh, ouais, j'ai bien compris; c'est pourquoi j'ai commencé par «et» pour indiquer que c'était une extension de votre pensée, plutôt qu'une contradiction.
KRyan

7

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

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


6

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 l' injection de dépendances , par exemple pour que je puisse basculer entre différentes implémentations ou pour injecter des objets fictifs à des fins de test. 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 y a différents composants . L'interface de composant / la 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 pourrait-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' Componentinterface, le conteneur peut simplement appeler la paint()méthode polymorphe . 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'utiliserais pour «définir une famille d'algorithmes, les encapsuler 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

Vous voyez beaucoup d'héritage et de polymorphisme dans la plupart des boîtes à outils d'interface utilisateur.

Par exemple, dans la boîte à outils JavaFX UI, Buttonhérite de ButtonBasequi hérite de Labeledqui hérite de Controlqui hérite de Regionqui hérite de Parentqui hérite de Nodequi 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 deNode enfant. Mais comment un volet sait-il quoi faire avec un bouton alors qu'il le voit simplement comme un objet Node générique? Cet objet pourrait être n'importe quoi. Le volet peut le faire car le bouton redéfinit les méthodes de Node avec toute 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 parfait exemple 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 à des fins très ésotériques. « Que diable est ReadOnlyJavaBeanLongPropertyBuilder? Est - il important? Est - ce que je dois comprendre ce qu'il 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.


3

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

  • Devicepeut être powerOn(), powerOff(), setSleep()et peut getSerialNumber().
  • SensorDevicepeut faire tout cela, et fournir des fonctions polymorphes telles que getMeasuredDimension(), getMeasure(), alertAt(threashhold)et autoTest().
  • bien entendu, getMeasure()ne seront pas mis en œuvre 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.

2

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

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 méthode de comparaison 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
   }
}

1

Voici un scénario réel pour le polymorphisme de table d'application Web / 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, PDF, etc.). Ainsi, par exemple, un Userpeut avoir plusieurs images de profil et un Productpeut également avoir de nombreuses images de produit. Les deux ont le comportement de télécharger et de stocker des images, ainsi que de redimensionner, de générer des miniatures, etc. Afin de rester SEC et de partager le comportement pour Picture, nous voulons rendre Picturepolymorphe afin qu'il puisse appartenir à la fois à Useret Product.

Dans Rails, je conçois 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 picturestable:

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_idet imageable_typesont utilisées par Rails en interne. Fondamentalement, imageable_typedétient le nom de la classe ( "User", "Product", etc.), et imageable_idest l'identifiant de l'enregistrement associé. Ainsi imageable_type = "User"et imageable_id = 1serait le record dans le userstableau avec id = 1.

Cela nous permet de faire des choses comme user.picturesaccéder aux photos de l'utilisateur, ainsi que product.picturesd'obtenir les photos d'un produit. Ensuite, tout le comportement lié à l'image est encapsulé dans la Photoclasse (et non une classe distincte pour chaque modèle qui a besoin de photos), de sorte que les choses restent SEC.

Plus de lecture: Rails associations polymorphes .


0

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 doté d'une 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 tandis 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 avec la bonne méthode au moment de la conformité lui-même.

J'espère que cela clarifie.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.