Aree Didattiche
Web FacileLinguaggi del WebLinguaggi EstensibiliGraficaProgrammazioneDatabaseWeb ServerSistemi OperativiNetworkingMobileBusiness
Aree Download
Software Script Flash Movies Sound Loops Templates Web Grafica Font

 

Sei un Webmaster? Iscriviti alla newsletter...

Polimorfismo e Duck Typing in Ruby

Articolo scritto da Alessio Saltarin il 31/07/2008
Pagina 1 di 2

Il polimorfismo è una tecnica di programmazione che consente l'utilizzo di parti di codice sorgente che, pur rimanendo inalterate, generano a run-time comportamenti diversi.

Creare codice polimorfico ha un significato preciso nella programmazione orientata agli oggetti: significa creare una tassonomia di classi che implementano tutte una stessa interfaccia.

Perciò se, ad esempio, la mia interfaccia definisce un metodo "calcolaArea", ogni classe che implementerà questa interfaccia avrà sicuramente un metodo "calcolaArea": questo ci permette di scrivere metodi polimorfici, metodi cioè in grado di cambiare il proprio algoritmo di esecuzione a seconda del tipo di oggetto che viene passato come argomento.

Il polimorfismo nei linguaggi OOP tradizionali

In Java - ma lo stesso discorso vale per il C++ e per qualsiasi altro linguaggio Object Oriented (OO) compilato, vedremo poi invece il caso di Ruby - ad esempio:
interface IFormaGeometrica
{
   void calcolaArea();
}

public class Triangolo implements IFormaGeometrica
{
 @Override
 public int calcolaArea()
 {
  return (this.base*this.altezza)/2;
 }
}
In questo caso definiamo l'interfaccia IFormaGeometrica che stabilisce che ogni oggetto che "è" una FormaGeometrica avrà un metodo calcolaArea- ad esempio la classe Triangolo, che è una FormaGeometrica, ha una sua implementazione di calcolaArea; il che ci permette di scrivere un programma in grado di calcolare le aree di qualsiasi forma geometrica, sia che questa sia presente oggi nel codice, sia che verrà implementata in futuro, senza cambiare il codice sorgente originale.

Infatti, se scrivo una classe Calcolatore:
public final class Calcolatore
{

 public static void main(String[] args)
 {
  Collection<IFormaGeometrica> forme =
          new ArrayList<IFormaGeometrica>();

  forme.add(new Triangolo());
  forme.add(new Quadrato());
  forme.add(new Pentagono());

  for (IFormaGeometrica g : forme)
  {
   System.out.println(g.calcolaArea());
  }
 }
}
questa può prendere come input una qualsiasi collezione di forme geometriche, a patto che ogni oggetto presente nella collezione implementi l'interfaccia IFormaGeometrica, e che cioè abbia in sostanza un metodo calcolaArea. Questo esempio in Java è puramente accademico: in realtà, probabilmente nel costruttore di ogni singola classe, dovremo prevedere in input le misure dei lati della forma geometrica, l'apotema e così via.

L'obiettivo è raggiunto: abbiamo scritto una classe in grado di stampare a video l'area di qualsiasi forma geometrica. Ripeto: sia che nel codice questa sia già stata implementata (come nel caso di Triangolo), sia che venga implementata in futuro.

Non solo: se l'implementazione del calcolo dell'area del Triangolo contenesse un bug, io posso modificare la classe Triangolo senza dover riscrivere la classe Calcolatore. Magari quest'ultima è stata deployata su un server che necessita di riavvio ad ogni modifica: in questo caso non dover cambiare il codice è un vantaggio notevole. Ma a parte questo, è sempre vantaggioso limitare le parti di codice che "cambiano" perchè ogni modifica porta con sè potenziali bug. Viceversa, è una buona norma di programmazione sapere sempre con certezza quali sono le parti di codice che rimangono invariate.

Quello che accade "dietro le scene" è che il compilatore si assicura che ogni oggetto all'interno del codice polimorfico del metodo main della classe Calcolatore implementi l'interfaccia. In questo modo, si dice usando un'espressione tratta dalla metodologia di design by contract, "il contratto è rispettato".

Se non lo fosse, se cioè in "forme" avessimo un oggetto di una classe che non implementa IFormaGeometrica avremmo un errore a compile time, ovvero non arriveremmo nemmeno a far girare il programma, perchè il compilatore si accorgerebbe dell'errore.

Il polimorfismo in Ruby

Ma cosa accade in Ruby? E' possibile in Ruby, che non è un linguaggio compilato ma interpretato, scrivere metodi polimorfici?

Sì, è certamente possibile, ma esiste una profonda differenza "filosofica" rispetto ai linguaggi OO e compilati, che

potremmo descrivere così. In Java e nella programmazione OO classica, per stabilire che un oggetto appartiene ad una determinata tipologia di oggetti (cioè implementa una certa interfaccia) occorre esplicitamente far derivare l'oggetto da una classe padre: in sostanza occorre usare le tecniche dell'ereditarietà (ereditando da una classe, da una classe astratta o da un'interfaccia).

Sarebbe come dire: per stabilire se questa che ho davanti è un'anatra, prendo il suo DNA e lo studio in laboratorio per vedere se è proprio quello di un'anatra.

In Ruby invece si utilizza il "test dell'anatra" (duck test) inventato da James Riley (vedi http://en.wikipedia.org/wiki/Duck_typing):

se cammina come un'anatra e starnazza come un'anatra, allora è un'anatra.

(il che è incidentalmente ciò che fa ognuno di noi quando vede un'anatra).

Cosa vuol dire questo? Vuol dire che in Ruby, e più in generale nei linguaggi di scripting orientati agli oggetti, come anche Python e Perl, non occorre specificare l'interfaccia, nè esplicitare relazioni di ereditarietà tra le classi.

Semplicemente l'interprete "si fida" del fatto che il programmatore, qualora passi al metodo polimorfico un oggetto che "dovrebbe avere" un certo metodo, in realtà ce l'abbia.
<<12>>

Versione di Stampa

Segnala ad un Amico!

Aggiungi ai Preferiti

RSS

Corsi online ed Ebook
Ruby e Ruby On Rails (Corso)Ruby e Ruby On Rails (Corso)
Creare software ed applicazioni Web con Ruby e ROR. A partire da 39 €.

 


© 2003 - 2010 Mr.Webmaster - Il portale dei Webmaster Italiani - Tutti i diritti riservati | Powered by IKIweb Internet Media S.r.l. - PIVA 02848390122