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 classe genitore Animal con un metodo speak() , e molti classi figlio che sovrascrivono questo metodo e ora puoi chiamare il metodo speak() su qualsiasi oggetto figlio, ad esempio:

Animal animal;

animal = dog;
animal.speak();

animal = cat;
animal.speak();
    
posta Christopher 22.07.2018 - 08:01
fonte

9 risposte

34

Stream è un ottimo esempio di polimorfismo .

Stream rappresenta una "sequenza di byte che può essere letta o scritta". 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 usa Stream non ha bisogno di preoccuparsi della provenienza dei byte. Solo che possono essere letti in sequenza.

Alcuni direbbero che Stream è un esempio errato di polimorfismo, poiché definisce molte "caratteristiche" che i suoi implementatori non supportano, come il flusso di rete che consente solo la lettura o la scrittura, ma non entrambi allo stesso tempo. O mancanza di ricerca. Ma questa è solo una questione di complessità, dato che Stream può essere suddiviso in molte parti che potrebbero essere implementate indipendentemente.

    
risposta data 22.07.2018 - 08:38
fonte
7

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

Per un esempio più puro orientato ai dati, potrebbe esserci una classe di base Serializable che fornisce un comune saveToStream() e loadFromStream() .

    
risposta data 22.07.2018 - 08:36
fonte
6

Si vede molta ereditarietà e polimorfismo nella maggior parte dei toolkit dell'interfaccia utente.

Ad esempio, nel toolkit della UI JavaFX, Button eredita da ButtonBase che eredita da Labeled che eredita da Control che eredita da Region che eredita da Parent che eredita da Node che eredita da Object . Molti livelli sovrascrivono alcuni metodi rispetto ai precedenti.

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

I toolkit dell'interfaccia utente hanno un altissimo significato nel mondo reale, rendendoli utili per insegnare sia per motivi accademici che pratici.

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

    
risposta data 22.07.2018 - 12:38
fonte
5

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

Una descrizione di alto 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à è per iniezione di dipendenza , ad es. in modo che io possa passare tra diverse implementazioni o iniettare oggetti finti per il test. Se so in anticipo che ci sarà solo un numero limitato di scelte possibili, 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 consiste nell'introdurre un'interfaccia per tale operazione e scrivere un'implementazione normale e un'implementazione fittizia di tale interfaccia e "iniettare" l'implementazione desiderata in fase di runtime. "Iniezione di dipendenza" è un termine complicato per "passare l'oggetto corretto come argomento".

Come esempio del mondo reale, attualmente sto lavorando a un problema di apprendimento automatico della macchina. Ho un algoritmo che richiede un modello di predizione. Ma voglio provare diversi algoritmi di apprendimento automatico. Così ho definito un'interfaccia. Di cosa ho bisogno dal mio modello di previsione? Dati alcuni esempi di input, la previsione e i relativi errori:

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

Il mio algoritmo utilizza una funzione di fabbrica che allena 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 l'una con l'altra. 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 modelli specifici in anticipo,
  • Posso facilmente sostituire i modelli e
  • Ho molta flessibilità nell'implementazione dei miei modelli.

Un caso d'uso classico del polimorfismo è nelle GUI. In una struttura GUI come Java AWT / Swing / ... ci sono diversi componenti . L'interfaccia componente / classe base descrive azioni come dipingersi sullo schermo o reagire ai clic del mouse. Molti componenti sono contenitori che gestiscono sottocomponenti. In che modo un simile contenitore potrebbe disegnarsi da solo?

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

Qui, il contenitore non ha bisogno di sapere in anticipo i tipi esatti dei sottocomponenti - finché si conformano all'interfaccia Component il contenitore può semplicemente chiamare il metodo polimorfo paint() . Questo mi dà la libertà di estendere la gerarchia delle classi 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 di soluzioni-problema sono denominate modelli di progettazione e alcune di esse 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 ognuno e renderli intercambiabili". L'esempio Java-AWT in cui un componente può contenere sotto-componenti è un esempio di composito .

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

    
risposta data 22.07.2018 - 13:42
fonte
3

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

  • Device può essere powerOn() , powerOff() , setSleep() e può getSerialNumber() .
  • SensorDevice può fare tutto questo e fornire funzioni polimorfiche come getMeasuredDimension() , getMeasure() , alertAt(threashhold) e autoTest() .
  • naturalmente, getMeasure() non verrà implementato allo stesso modo per un sensore di temperatura, un rilevatore di luce, un rilevatore di suoni o un sensore volumetrico. E, naturalmente, ognuno di questi sensori più specializzati potrebbe avere alcune funzioni aggiuntive disponibili.
risposta data 22.07.2018 - 08:53
fonte
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 si dice a un oggetto di "fare la sua cosa". Think Save, Load, Initialize, Dispose, ProcessData, GetStatus.

    
risposta data 22.07.2018 - 09:17
fonte
2

Il mio primo utilizzo pratico del polimorfismo era nell'implementazione di Heap in java.

Avevo una classe base con l'implementazione dei metodi insert, removeTop where la differenza tra Heap max e min sarebbe solo la modalità di confronto del metodo.

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
   }
}
    
risposta data 22.07.2018 - 15:56
fonte
1

Ecco uno scenario reale per polimorfismo della tabella di app / database del database :

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, un User potrebbe avere più immagini del profilo e un Product potrebbe anche avere molte immagini del prodotto. Entrambi hanno il comportamento di caricare e archiviare immagini, così come ridimensionare, generare miniature, ecc. Per rimanere DRY e condividere il comportamento per Picture , vogliamo rendere Picture polymorphic in modo che possa appartenere a entrambi User e Product .

In Rails disegnerei 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 tabella 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

Le colonne imageable_id e imageable_type vengono utilizzate internamente da Rails. Fondamentalmente, imageable_type contiene il nome della classe ( "User" , "Product" , ecc.) E imageable_id è l'id del record associato. Quindi imageable_type = "User" e imageable_id = 1 sarebbero il record nella tabella users con id = 1 .

Questo ci consente di fare cose come user.pictures per accedere alle immagini dell'utente, nonché product.pictures per ottenere le immagini di un prodotto. Quindi, tutto il comportamento relativo all'immagine è incapsulato nella classe Photo (e non in una classe separata per ogni modello che ha bisogno di foto), quindi le cose vengono mantenute DRY.

Altre letture: Associazioni polimorfiche delle rotaie .

    
risposta data 22.07.2018 - 18:57
fonte
0

Ci sono molti algoritmi di ordinamento disponibili come bubble sort, insertion sort, quick sort, heap sort ecc. e hanno una complessità diversa e quale è l'optimum da usare dipende da vari fattori (es: dimensione dell'array)

Il client fornito con l'interfaccia di ordinamento riguarda solo la fornitura dell'array come input e quindi la ricezione dell'array ordinato. Durante il runtime, in base a determinati fattori, è possibile utilizzare l'implementazione di ordinamento appropriata. Questo è uno degli esempi reali di dove viene usato il polimorfismo.

Quello che ho descritto sopra è un esempio di polimorfismo di runtime, mentre il sovraccarico di metodo è un esempio di polimorfismo del tempo di compilazione in cui il compilatore che dipende dai tipi di parametri i / p e o / p e il numero di parametri vincola il chiamante con il metodo corretto al tempo di compilazione stesso.

Spero che questo chiarisca.

    
risposta data 22.07.2018 - 13:27
fonte

Leggi altre domande sui tag