Polimorfismo

In relazione al paradigma della programmazione orientata agli oggetti, in un precedente articolo abbiamo parlato della ereditarietà dicendo:

Con la tecnica della ereditarietà costruiamo una nuova classe derivata a partire da una classe base da cui verranno ereditate le proprietà e i metodi. La particolarità di questa tecnica è che, se ne dovessimo avere la necessità, potremmo modificare uno o più dei suoi metodi a nostro piacimento specializzando così gli oggetti che creeremo del tipo della nostra classe derivata.

La tecnica di riscrittura dei metodi ereditati prende il nome di override. I metodi, così riscritti, vengono definiti metodi polimorfi.

Il metodo che si intende riscrivere nella classe derivata, deve avere lo stesso nome e la stessa firma del metodo presente nella classe base.

Facciamo ora un esempio per capire un po’ meglio come stanno le cose.

Esempio di un metodo polimorfo

Supponiamo di avere la classe base “pianta” e di creare, a partire da questa, due classi derivate che ne ereditino sia le proprietà che i metodi. Chiameremo queste due classi derivate “albero” e “rampicante”.

Supponiamo ora che nella classe base sia stato dicharato il metodo “sviluppo_radice” che fungerà, come vedremo più avanti, da interfaccia.

A questo punto riscriveremo ad hoc nelle classi derivate il metodo “sviluppo_radice” in modo tale che la chiamata di questo metodo fatta su un oggetto del tipo “albero” si comporti diversamente dalla stessa chiamata fatta su un oggetto del tipo “rampicante”.

Il metodo “sviluppo_radice” sarà quindi un metodo polimorfo, e la sua riscrittura operata nelle due classi derivate sarà stata una operazione di override.

Vantaggi del polimorfismo

Come abbiamo avuto modo di vedere, il polimorfismo ci permette di avere una interfaccia unica verso tutti i possibili altri programmi (definiti programmi client) che useranno le nostre classi derivate.

In questo modo avremo due vantaggi:

  1. Sarà sempre possibile estendere le modalità di risposta ad una stessa chiamata al metodo interfaccia, includendo tutte le possibili dichiarazioni dei metodi polimorfi in una nostra specifica classe contenitore a cui dovranno fare riferimento i programmi client.
  2. Sarà sempre possibile migliorare le modalità di risposta modificando il solo codice contenuto nei metodi polimorfi senza che i programmi client debbano essere modificati.

Esempio di polimorfismo in FreeBASIC

Vediamo ora un piccolo esempio della applicazione del polimorfismo in FreeBASIC.

''Classe base "pianta"
type pianta
  public:
    dim as string nome
    declare constructor(byref _nome as string)
    declare function sviluppo_radice() as string
  protected:
    ''impedisce l'uso del costruttore di default
    declare constructor()
end type
constructor pianta(byref _nome as string)
  this.nome = _nome
end constructor
function pianta.sviluppo_radice() as string
  return "sono una generica radice di una pianta"
end function

''Classe derivata "albero"
type albero extends pianta
  public:
    declare constructor(byref _nome as string)
    declare function sviluppo_radice() as string
end type
constructor albero(byref _nome as string)
  base(_nome)
end constructor
function albero.sviluppo_radice() as string
  return "sono la radice di un albero di"
end function

''Classe derivata "rampicante"
type rampicante extends pianta
  public:
    declare constructor(byref _nome as string)
    declare function sviluppo_radice() as string
end type
constructor rampicante(byref _nome as string)
  base(_nome)
end constructor
function rampicante.sviluppo_radice() as string
  return "sono la radice del rampicante"
end function

''Creazione di un oggetto di tipo "albero"
dim as albero mio_albero = albero("pioppo")

''Creazione di un oggetto di tipo "rampicante"
dim as rampicante mio_rampicante = rampicante("edera")

''Invoca il metodo polimorfo
print mio_albero.sviluppo_radice() & " " & mio_albero.nome
print mio_rampicante.sviluppo_radice() & " " & mio_rampicante.nome

sleep

Bene. Con questo articolo finiamo la sintetica panoramica sulla programmazione ad oggetti in FreeBASIC.

Ora non resta che sperimentare. 😉

Overloading

Oggi discuteremo di un altro importante costrutto specifico del paradigma della programmazione orientata agli oggetti: vedremo insieme cosa si intende per overloading (in italiano: sovraccarico) e come questa tecnica sia implementata in FreeBASIC.

Introduzione

Abbiamo visto nell’articolo introduttivo alla programmazione orientata agli oggetti in FreeBASIC che tra i membri di una classe, oltre alle proprietà ci sono anche i cosiddetti metodi che, in ultima analisi, non sono altro che procedure: ovvero subroutines o funzioni.

Come ben sappiamo ogni procedura è caratterizzata da uno specifico nome, dalla possibilità di ricevere degli argomenti e, nel caso delle funzioni, anche di restituire un valore di ritorno.

La lista degli argomenti viene definita la firma (signature) della procedura.

La tecnica di overloading consiste nello scrivere più procedure aventi lo stesso nome, ma firma differente.

Questa tecnica viene normalmente applicata anche alla procedura costruttore.

Utilità della tecnica di overloading

Arrivati a questo punto è lecito domandarci: ma a che ci serve applicare questa tecnica?

La risposta è semplice:

  • Se sovraccarichiamo un costruttore abbiamo la possibilità di creare degli oggetti (istanze della nostra classe) inizializzandoli in maniera differente.
  • Se sovraccarichiamo un metodo abbiamo la possibilità di specializzare un certo comportamento in base proprio ai parametri passati come argomenti.

Esempio di overloading su un metodo

''definizione della classe "sommatore"
type sommatore
  public:
    ''dichiarazione del metodo "somma" sovraccaricato
    declare function somma(a as integer, b as integer) as integer
    declare function somma(a as string, b as string) as string
  ''dimensionamento della proprietà "etichetta"
  dim as string etichetta = "Il risultato e': "
end type

''definizione delle funzioni "somma"
function sommatore.somma(a as integer, b as integer) as integer
  return a+b
end function
function sommatore.somma(a as string, b as string) as string
  return a+b
end function

''creazione dell'oggetto istanza della classe "sommatore"
dim as sommatore mio_sommatore

''primo caso: somma con valori interi
dim as integer c, d
c = 3
d = 4
print mio_sommatore.etichetta & mio_sommatore.somma(c,d)

''secondo caso: somma con valori stringa
dim as string e, f
e = "3"
f = "4"
print mio_sommatore.etichetta & mio_sommatore.somma(e,f)

sleep ''pausa

Nota: se applichiamo la tecnica di overloading alle procedure che sono dei membri di una classe, non è necessario specificarlo esplicitamente, ma se vogliamo applicarla alle normali procedure, allora è obbligatorio far seguire al nome della procedura la parola-chiave overload, almeno per la prima procedura che si vuole sovraccaricare.

Bene. Un altro piccolo passo è stato fatto. 🙂

Nel prossimo articolo parleremo del concetto di polimorfismo.

Associazione ed Ereditarietà

Con questo nuovo articolo cercheremo di esplorare due importanti concetti della programmazione orientata agli oggetti così come vengono implementati in FreeBASIC:

  • associazione (per composizione o aggregazione)
  • ereditarietà

Introduzione

Quando studiamo un modo per modellare in una classe gli oggetti del mondo reale o astratto con il quale dobbiamo lavorare, è molto probabile che arriveremo ad avere a che fare con classi che sono in relazione con altre classi.

Per scegliere il tipo di relazione che vogliamo stabilire tra le classi, dobbiamo porci la seguente domanda:

  1. Rispetto ad una generica classe base da cui vogliamo partire per creare la nostra classe derivata, quest’ultima, oltre a prevedere le stesse proprietà e gli stessi metodi, dovrà avere la possibilità di adattarli modificandoli?

Se a questa domanda rispondiamo che non abbiamo la necessità di modificare i membri della classe base, allora sceglieremo la tecnica della associazione, mentre se dovessimo avere la necessità di modificarne qualcuno, allora sceglieremo la tecnica della ereditarietà.

Vediamo ora più in dettaglio queste differenti tecniche.

Associazione

In FreeBASIC la tecnica della associazione può essere adottata in due differenti modalità.

Associazione per composizione

Con la tecnica della associazione per composizione possiamo realizzare una classe derivata di una certa complessità a partire da classi base più semplici, ciascuna delle quali manterrà inalterate le sue proprietà e i suoi metodi.

Volendo fare un esempio, potremmo immaginare le varie parti che compongono una pianta: la radice, il fusto, la foglia, il fiore, il frutto.

Ciscuna classe base può essere a sua volta la classe derivata di altre classi base. E così via.

La particolarità della tecnica per composizione è che al momento della distruzione in memoria dell’oggetto del tipo della classe derivata, anche tutti gli oggetti del tipo delle classi base verranno distrutti in quanto di fatto erano contenuti nell’oggetto composto.

Tornando all’esempio della pianta, potremmo dire che, una volta che sia morta, anche le singole parti che la componevano andranno incontro al normale processo di decomposizione.

Associazione per aggregazione

Con la tecnica della associazione per aggregazione realizziamo una classe derivata che ha dei collegamenti con altre classi base di cui sfrutterà le proprietà e i metodi anche in questo caso senza alterarli.

La particolarità della aggregazione è che la classe derivata contiene soltanto dei puntatori alle altre classi base: ciò significa che nel momento in cui distruggiamo in memoria l’oggetto del tipo della classe derivata, gli altri oggetti del tipo delle classi base che erano stati aggregati non vengono distrutti in quanto sono a tutti gli effetti delle entità indipendenti.

Torniamo all’esempio della pianta. Possiamo dire che le classi base, a cui la classe derivata pianta deve associarsi, sono la classe base terra, acqua, sole, aria. In questo caso alla morte della pianta non vi è nessun effetto distruttivo sulle classi base.

Ereditarietà

Con la tecnica della ereditarietà costruiamo una nuova classe derivata a partire da una classe base da cui verranno ereditate le proprietà e i metodi. La particolarità di questa tecnica è che, se ne dovessimo avere la necessità, potremmo modificare uno o più dei suoi metodi a nostro piacimento specializzando così gli oggetti che creeremo del tipo della nostra classe derivata (vedi l’articolo dedicato al polimorfismo).

In FreeBASIC non è prevista la cosiddetta ereditarietà multipla: ovvero la possibilità di creare una classe derivata ereditando le proprietà e i metodi da più classi base.

Ancora una volta prendiamo come esempio la nostra piantina che dovrà necessariamente ereditare le sue caratteristiche e le sue funzionalità da un seme.

Costruiamo una classe derivata

''Classe derivata "pianta"
''  ereditarietà -> la pianta è: un seme
''  composizione -> la pianta ha: radice, fusto, foglia, fiore, frutto
''  aggregazione -> la pianta usa: terra, acqua, sole, aria

''classe base per la ereditarieta'
type seme
  public:
    dim as string nome
    declare constructor(byref _nome as string)
  protected: ''impedisce l'uso del costruttore di default
    declare constructor()
  ''[…]
end type
constructor seme(byref _nome as string)
  this.nome = _nome
end constructor

''classi base per la composizione
type fiore
  public:
     dim as integer numero_fiori
end type
''…

''classi base per l'aggregazione
type terra
  public:
    dim as string tipo
    declare constructor(byref _tipo as string)
end type
constructor terra(byref _tipo as string)
  this.tipo = _tipo
end constructor
''…

''classe derivata "pianta"
type pianta extends seme ''ereditarieta'
  public:
    dim as fiore f ''composizione
    ''…
    dim as terra ptr t ''aggregazione
    ''…
    declare constructor(byref _nome as string, _
                        byref _f as fiore)
end type
constructor pianta(byref _nome as string, _
                   byref _f as fiore)
   base(_nome) '' vedi nota piu' sotto
   this.f = _f
end constructor

''creazione dell'oggetto "mia_pianta" del tipo della classe derivata
dim as pianta mia_pianta = pianta("Non ti scordar di me",type<fiore>(6))
dim as terra ptr t1 = new terra("buona") ''creo un oggetto "terra"
mia_pianta.t = t1 ''aggrego alla pianta l'oggetto "terra"

print "Nome pianta  : " & mia_pianta.nome
print "Numero fiori : " & mia_pianta.f.numero_fiori
print "Tipo terra   : " & mia_pianta.t->tipo


delete t1 ''distruzione dell'oggetto aggregato
mia_pianta.t = 0

sleep

Nota sulla istruzione base

L’istruzione base è una chiamata esplicita al costruttore della classe base ereditata dalla classe derivata. Questa istruzione deve comparire al primo posto nel costruttore in cui viene utilizzata.

Che fatica! Per oggi ci fermiamo qui. 🙂

Nel prossimo articolo discuteremo del concetto di overloading.

Incapsulamento

Nella programmazione orientata agli oggetti, uno dei concetti fondamentali è quello dell’incapsulamento.

Quando definiamo una nostra classe, è bene restringere l’ambito di visibilità delle proprietà e dei metodi ai soli metodi che definiremo pubblici che fungeranno quindi da interfaccia verso il mondo esterno.

Con questa strategia di programmazione abbiamo i seguenti due vantaggi:

  • Possiamo modificare liberamente la logica dei metodi incapsulati senza che queste modifiche alterino il modo con cui altri oggetti si interfacciano con l’oggetto del tipo della nostra classe.
  • Impediamo l’accesso diretto alle proprietà in modo tale possiamo sviluppare del codice di controllo per impedire input non corretti.

Definizione dei diritti di accesso ai membri

In FreeBASIC abbiamo tre diverse tipologie per definire i diritti di accesso (ambito di visibilità) ai membri di una classe (proprietà e metodi):

  1. public: in mancanza di una definizione esplicita da parte del programmatore, public è la classificazione di default. Questa classificazione permette il libero accesso al membro così definito non soltanto da parte di tutti gli altri membri della classe, ma anche dal codice e dalle procedure definite a livello dello stesso modulo: ovvero dello stesso file sorgente.
  2. protected: questa classificazione restringe l’accesso ad un determinato membro di una classe sia ai membri della stessa classe che a quelli di eventuali classi derivate.
  3. private: questa classificazione permette l’accesso ad un membro di una classe solo ed esclusivamente ai soli membri della classe.

Questa tre tipologie di classificazione hanno lo stesso effetto anche per le procedure costruttore e distruttore.

Buone pratiche di programmazione

Quando adottiamo il paradigma della programmazione orientata agli oggetti, in relazione al tema dei diritti di accesso ai membri di una classe sono state consolidate nel tempo delle cosiddette buone pratiche di programmazione che pervedono di adottare queste modalità di definizione del codice:

  • proprietà: è bene classificarle come private.
  • metodi per la logica di funzionamento interna dell’oggetto: è bene classificarli come protected.
  • metodi interfaccia: vengono classificati come public.

Esempio

''definizione della classe strumento_musicale
type strumento_musicale
  public:
    declare sub imposta_strumento(s as string)
    declare function ottieni_strumento() as string
  protected:
    declare function controllo_tipo(s as string) as string
  private:
    dim as string strumento
end type

''implementazione dei metodi della classe strumento_musicale
sub strumento_musicale.imposta_strumento(s as string)
  if controllo_tipo(s) = "OK" then
    this.strumento = s
  else
    this.strumento = "non previsto nella nostra scuola."
  end if
end sub

function strumento_musicale.ottieni_strumento() as string
  return this.strumento
end function

function strumento_musicale.controllo_tipo(s as string) as string
  if s = "chitarra" then
    return "OK"
  else
    return ""
  end if
end function

''crea un oggetto del tipo strumento_musicale
dim as strumento_musicale mio_strumento_musicale
dim as string mio_strumento
input "Qual e' il tuo strumento"; mio_strumento 
mio_strumento_musicale.imposta_strumento(mio_strumento)
print "Lo strumento che hai inserito e': " & _
mio_strumento_musicale.ottieni_strumento()

''una eventuale chiamata diretta al metodo dichiarato
''protected genererebbe, in fase di compilazione, un
''errore di "accesso illegale ad un membro"
''mio_strumento_musicale.controllo_tipo(mio_strumento)

Bene! Abbiamo fatto insieme un altro piccolo passo nel mondo della programmazione ad oggetti.

Nel prossimo articolo vedremo come in FreeBASIC si applicano le tecniche di associazione ed ereditarietà. 🙂