Come viene utilizzato il polimorfismo nel mondo reale? [chiuso]


17

Sto cercando di capire come il polimorfismo viene utilizzato in un progetto di vita reale, ma posso trovare solo l'esempio classico (o qualcosa di simile ad esso) di avere una Animalclasse genitore con un metodo speak()e molte classi figlio che sovrascrivono questo metodo, e ora puoi chiamare il metodo speak()su uno qualsiasi degli oggetti figlio, ad esempio:

Animal animal;

animal = dog;
animal.speak();

animal = cat;
animal.speak();



1
Le collezioni, che vedi e usi ogni giorno, sono sufficienti per capire cos'è il polimorfismo. Ma come utilizzare efficacemente il polimorfismo nella risoluzione dei problemi è un'abilità che acquisisci di più per esperienza e non semplicemente per discutere. Vai avanti e sporca le mani.
Durgadass,

Se si dispone di un set di tipi che supporteranno tutti un qualche tipo di interfaccia minima (ad esempio, un set di oggetti che devono essere disegnati), un'interfaccia è in genere una buona soluzione per nascondere le differenze tra gli oggetti dalla chiamata per disegnarla. Inoltre, se stai creando (o lavorando) un'API con metodi in grado di servire un oggetto base e un numero significativo di tipi che ereditano da esso più o meno allo stesso modo , il polimorfismo può essere il modo migliore per astrarre le differenze tra quei tipi.
jrh

In generale, se si utilizzano frequentemente metodi sovraccarichi per gestire tipi diversi e il codice è simile, o se si scrive if(x is SomeType) DoSomething()spesso, potrebbe valere la pena usare il polimorfismo. Per me il polimorfismo è una decisione simile a quando fare un metodo separato, se ho scoperto che ho ripetuto il codice alcune volte di solito lo refactoring in un metodo, e se trovo che sto facendo if object is this type do thiscodice spesso, potrebbe essere vale la pena refactoring e aggiungere un'interfaccia o una classe.
jrh

Risposte:


35

Lo stream è un ottimo esempio di polimorfismo.

Stream rappresenta una "sequenza di byte che è possibile leggere o scrivere". Ma questa sequenza può provenire da file, memoria o molti tipi di connessioni di rete. Oppure può fungere da decoratore, che avvolge il flusso esistente e trasforma i byte in qualche modo, come la crittografia o la compressione.

In questo modo, il client che utilizza Stream non deve preoccuparsi della provenienza dei byte. Solo che possono essere letti in sequenza.

Qualcuno potrebbe dire che Streamè un esempio errato di polimorfismo, perché definisce molte "caratteristiche" che i suoi implementatori non supportano, come il flusso di rete che consente solo la lettura o la scrittura, ma non entrambe allo stesso tempo. O mancanza di ricerca. Ma questa è solo una questione di complessità, poiché Streampuò essere suddivisa in molte parti che potrebbero essere implementate in modo indipendente.


2
In linguaggi con eredità multipla e virtuale come C ++, questo esempio può persino dimostrare il modello "temuto diamante" ... derivando classi di flusso di input e output da una classe di flusso di base ed estendendole entrambe per creare un flusso di I / O
gyre

2
@gyre E fatto bene, non c'è motivo di "temere" il motivo a diamante. È importante essere consapevoli della controparte opposta nel diamante e non causare conflitti di nome con esso è importante, è una sfida, fastidiosa e una ragione per evitare il modello di diamante dove possibile ... ma non andare troppo in là temendo quando semplicemente avere, diciamo, una convenzione di denominazione potrebbe risolvere i problemi.
KRyan,

I +1 Streamsono il mio esempio di polimorfismo preferito di tutti i tempi. Non provo nemmeno più a insegnare alla gente il modello difettoso di "animale, mammifero, cane", Streamma faccio un lavoro migliore.
Pharap,

@KRyan Non stavo esprimendo i miei pensieri chiamandolo il "temuto diamante", l'ho appena sentito come tale. Sono completamente d'accordo; Penso che sia qualcosa che ogni sviluppatore dovrebbe essere in grado di avvolgere la testa e utilizzare in modo appropriato.
gyre,

@gyre Oh, sì, in realtà l'ho capito; ecco perché ho iniziato con "e" per indicare che era un'estensione del tuo pensiero, piuttosto che una contraddizione.
KRyan,

7

Un tipico esempio di giochi potrebbe essere una classe base Entity, che fornisce membri comuni come draw()o update().

Per un esempio più puro orientato ai dati potrebbe esserci una classe base che Serializablefornisce un valore comune saveToStream()e loadFromStream().


6

Esistono diversi tipi di polimorfismo, quello di interesse è solitamente il polimorfismo di runtime / dispacciamento dinamico.

Una descrizione di altissimo livello del polimorfismo di runtime è che una chiamata di metodo fa cose diverse a seconda del tipo di runtime dei suoi argomenti: l'oggetto stesso è responsabile della risoluzione di una chiamata di metodo. Ciò consente un'enorme flessibilità.

Uno dei modi più comuni per utilizzare questa flessibilità è l' iniezione di dipendenza , ad esempio in modo che io possa passare da implementazioni diverse o iniettare oggetti simulati per i test. Se so in anticipo che ci sarà solo un numero limitato di possibili scelte, potrei provare a codificarle con i condizionali, ad esempio:

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

Questo rende il codice difficile da seguire. L'alternativa è introdurre un'interfaccia per quella operazione e scrivere un'implementazione normale e un'implementazione fittizia di quell'interfaccia e "iniettare" l'implementazione desiderata in fase di esecuzione. "Iniezione delle dipendenze" è un termine complicato per "passare l'oggetto corretto come argomento".

Come esempio nel mondo reale, attualmente sto lavorando a un tipo di problema di apprendimento automatico. Ho un algoritmo che richiede un modello di previsione. Ma voglio provare diversi algoritmi di apprendimento automatico. Quindi ho definito un'interfaccia. Di cosa ho bisogno dal mio modello di previsione? Dato un esempio di input, la previsione e i suoi errori:

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

Il mio algoritmo utilizza una funzione di fabbrica che forma un modello:

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

Ora ho varie implementazioni dell'interfaccia del modello e posso confrontarle tra loro. Una di queste implementazioni prende in realtà altri due modelli e li combina in un modello potenziato. Quindi grazie a questa interfaccia:

  • il mio algoritmo non ha bisogno di conoscere in anticipo modelli specifici,
  • Posso facilmente scambiare modelli e
  • Ho molta flessibilità nell'implementazione dei miei modelli.

Un classico caso d'uso del polimorfismo è nelle GUI. In un framework GUI come Java AWT / Swing / ... ci sono diversi componenti . L'interfaccia componente / la classe base descrive azioni come dipingere se stesso sullo schermo o reagire ai clic del mouse. Molti componenti sono contenitori che gestiscono i sottocomponenti. Come potrebbe attirare un tale contenitore?

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

Qui, il contenitore non ha bisogno di conoscere in anticipo i tipi esatti dei sottocomponenti - fintanto che sono conformi Componentall'interfaccia, il contenitore può semplicemente chiamare il paint()metodo polimorfico . Questo mi dà la libertà di estendere la gerarchia della classe AWT con nuovi componenti arbitrari.

Ci sono molti problemi ricorrenti durante lo sviluppo del software che possono essere risolti applicando il polimorfismo come tecnica. Queste ricorrenti coppie problema-soluzione sono chiamate modelli di progettazione e alcune sono raccolte nel libro con lo stesso nome. Nei termini di quel libro, il mio modello di apprendimento automatico iniettato sarebbe una strategia che uso per "definire una famiglia di algoritmi, incapsulare ciascuno di essi e renderli intercambiabili". L'esempio Java-AWT in cui un componente può contenere sottocomponenti è un esempio di un composto .

Ma non tutti i progetti devono utilizzare il polimorfismo (oltre a consentire l'iniezione di dipendenza per i test unitari, che è davvero un buon caso d'uso). La maggior parte dei problemi è altrimenti molto statica. Di conseguenza, spesso le classi e i metodi non vengono utilizzati per il polimorfismo, ma semplicemente come spazi dei nomi convenienti e per il grazioso metodo di sintassi delle chiamate. Ad esempio, molti sviluppatori preferiscono le chiamate di metodo come account.getBalance()su una chiamata di funzione in gran parte equivalente Account_getBalance(account). Questo è un approccio perfettamente perfetto, è solo che molte chiamate di "metodo" non hanno nulla a che fare con il polimorfismo.


6

Vedi un sacco di eredità e polimorfismo nella maggior parte dei toolkit dell'interfaccia utente.

Ad esempio, nel toolkit JavaFX UI, Buttonereditato da ButtonBasecui ha ereditato da Labeledcui ha ereditato da Controlcui ha ereditato da Regioncui ha ereditato da Parentcui eredita daNode cui eredita da Object. Molti livelli sovrascrivono alcuni metodi dai precedenti.

Quando vuoi che quel pulsante appaia sullo schermo, lo aggiungi a Pane, che può accettare qualsiasi cosa ereditaNode bambino. Ma come fa un riquadro a sapere cosa fare con un pulsante quando lo vede come un oggetto Node generico? Quell'oggetto potrebbe essere qualsiasi cosa. Il riquadro può farlo perché il Pulsante ridefinisce i metodi di Nodo con qualsiasi logica specifica del pulsante. Il riquadro chiama semplicemente i metodi definiti in Nodo e lascia il resto all'oggetto stesso. Questo è un perfetto esempio di polimorfismo applicato.

I toolkit dell'interfaccia utente hanno un significato molto elevato nel mondo reale, rendendoli utili per insegnare sia per ragioni accademiche che pratiche.

Tuttavia, i toolkit dell'interfaccia utente presentano anche un significativo svantaggio: tendono ad essere enormi . Quando un ingegnere informatico neofita cerca di capire il funzionamento interno di un framework UI comune, incontrano spesso oltre un centinaio di classi , la maggior parte delle quali serve a scopi molto esoterici. "Che diavolo è un ReadOnlyJavaBeanLongPropertyBuilder? È importante? Devo devo capire che cosa è buono per?" I principianti possono facilmente perdersi in quella tana di coniglio. Quindi potrebbero fuggire nel terrore o rimanere in superficie dove imparano solo la sintassi e cercano di non pensare troppo a ciò che sta realmente accadendo sotto il cofano.


3

Sebbene ci siano già dei buoni esempi qui, un altro è quello di sostituire gli animali con i dispositivi:

  • Devicepuò essere powerOn(), powerOff(), setSleep()e lattina getSerialNumber().
  • SensorDevicepuò fare tutto questo, e fornire funzioni polimorfiche, come getMeasuredDimension(), getMeasure(), alertAt(threashhold)e autoTest().
  • ovviamente, getMeasure()non sarà implementato allo stesso modo per un sensore di temperatura, un rivelatore di luce, un rilevatore di suoni o un sensore volumetrico. E, naturalmente, ciascuno di questi sensori più specializzati può avere alcune funzioni aggiuntive disponibili.

2

La presentazione è un'applicazione molto comune, forse la più comune è ToString (). Che è fondamentalmente Animal.Speak (): dici a un oggetto di manifestarsi.

Più in generale, dici a un oggetto di "fare la sua cosa". Pensa a Salva, Carica, Inizializza, Disponi, ProcessData, GetStatus.


2

Il mio primo uso pratico del polimorfismo fu un'implementazione di Heap in Java.

Avevo una classe base con l'implementazione dei metodi insert, removeTop dove la differenza tra Heap max e min sarebbe solo come funziona il confronto dei metodi.

abstract class Heap {  

 abstract boolean compare ( int x , int y );

 boolean insert(int x ) { ... }

 int removeTop() { ... }
}

Quindi, quando volevo avere MaxHeap e MinHeap, potevo semplicemente usare l'ereditarietà.

class MaxHeap extends Heap {

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

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

1

Ecco uno scenario di vita reale per il polimorfismo della tabella app / database Web :

Uso Ruby on Rails per sviluppare app Web e una cosa che molti dei miei progetti hanno in comune è la possibilità di caricare file (foto, PDF, ecc.). Ad esempio, a Userpotrebbe avere più immagini del profilo e Productpotrebbe anche avere molte immagini del prodotto. Entrambi hanno il comportamento di caricare e archiviare immagini, nonché di ridimensionare, generare miniature, ecc. Al fine di rimanere SECCHI e condividere il comportamento per Picture, vogliamo rendere Picturepolimorfico in modo che possa appartenere a entrambi Usere Product.

In Rails progetterei i miei modelli come tali:

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

e una migrazione del database per creare la picturestabella:

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

Le colonne imageable_ide imageable_typesono utilizzate internamente da Rails. In sostanza, imageable_typecontiene il nome della classe ( "User", "Product", etc.), ed imageable_idè l'id del record associato. Quindi imageable_type = "User"e imageable_id = 1sarebbe il record nella userstabella con id = 1.

Questo ci consente di fare cose come user.picturesaccedere alle immagini dell'utente, nonché product.picturesdi ottenere le immagini di un prodotto. Quindi, tutto il comportamento relativo all'immagine viene incapsulato nella Photoclasse (e non in una classe separata per ogni modello che necessita di foto), quindi le cose vengono mantenute ASCIUTTE.

Altre letture: associazioni polimorfiche di Rails .


0

Esistono molti algoritmi di ordinamento disponibili come ordinamento a bolle, ordinamento per inserzione, ordinamento rapido, ordinamento heap, ecc. E presentano una complessità diversa e quale è ottimale utilizzare dipende da vari fattori (es: dimensioni dell'array)

Il client fornito con l'interfaccia di ordinamento si preoccupa solo di fornire l'array come input e quindi ricevere l'array ordinato. Durante il runtime, in base a determinati fattori, è possibile utilizzare un'implementazione di ordinamento appropriata. Questo è uno degli esempi del mondo reale in cui viene utilizzato il polimorfismo.

Quello che ho descritto sopra è un esempio di polimorfismo di runtime mentre il sovraccarico del metodo è un esempio di polimorfsim di tempo di compilazione in cui il complier dipende dai tipi di parametro i / p e o / p e dal numero di parametri associa il chiamante con il metodo giusto al tempo stesso di complie.

Spero che questo chiarisca.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.