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à. 🙂

Immaginare un modo nuovo di fare coding

Premessa

Questo articolo non vuole inserirsi nel dibattito se sia corretto o meno avvicinare i bambini al coding, ovvero alla programmazione informatica, durante la scuola primaria con l’obiettivo di sviluppare in loro il pensiero computazionale già nei primi dieci anni di vita.

Anzi, personalmente ho firmato la petizione ai responsabili dell’istruzione nell’UE e negli Stati membri per concedere il diritto ad asili nido, scuole materne e scuole elementari di essere liberi dallo schermo, nella convinzione che ogni cosa deve essere fatta a suo tempo.

Il pensiero computazionale è un processo mentale per la creazione di un algoritmo, ovvero di una sequenza finita di passi elementari (istruzioni), traducibili poi in un linguaggio di programmazione (codice sorgente) in grado di permettere ad un computer di risolvere un problema.

Sull’introduzione delle competenze digitali nella scuola si visiti il sito web del MIUR (Ministero dell’Istruzione dell’Università e della Ricerca) dedicato al Piano Nazionale Scuola Digitale.

Si veda inoltre il progetto CS Unplugged che intende avvicinare i bambini al mondo del coding senza utilizzare direttamente il computer.

Come adulti, però, dobbiamo sentirci liberi di esplorare la possibilità, se esiste, di fare coding antroposoficamente così come esiste per esempio un modo antroposofico di educare (pedagogia Waldorf), di coltivare (agricoltura biodinamica) o di curare (medicina antroposofica).

Scopo

Lo scopo è quello di provare a gettare uno sguardo non meramente utilitaristico sul mondo dell’Informatica.

Per intraprendere questo percorso di ricerca ci si ispira alla concezione filosofica dell’Idealismo oggettivo così come è stata veicolata dall’Antroposofia.

Coding e creatività

Non c’è dubbio che nella programmazione informatica la creatività giochi un ruolo fondamentale.

Non basta infatti conoscere un linguaggio di programmazione per risolvere un dato problema, e molto spesso non basta neanche una disciplinata capacità di analisi unita alla conoscenza della natura del problema che si sta affrontando. Tutte cose, ovviamente, indispensabili.

Quando ci si trova bloccati, quando sembra che non ci sia più una via d’uscita, ecco che una illuminazione, un guizzo della fantasia, una fulminea intuizione, ci fanno scorgere finalmente la soluzione, e soltanto allora possiamo tornare a mettere mano al codice sorgente scrivendo o correggendo il nostro algoritmo.

Quando il pensiero concepisce un algoritmo efficace ed efficiente, lo si descrive spesso come una soluzione elegante, e non è raro sentire accostare a questa soluzione il concetto di bellezza.

Tutto questo ha a che fare con l’ampio tema relativo alla risoluzione dei problemi, il cosiddetto problem solving, che, per inciso, riguarda tutti noi, sia che si stia lavorando nell’ambito informatico, sia che si debba semplicemente decidere come organizzarsi per andare a fare la spesa.

Possiamo quindi dire che il pensiero computazionale, oltre ad essere una facoltà umana di uso generalizzato e quotidiano, è strettamente legato al pensiero creativo.

Arte e Antroposofia

Nell’ambito dell’Antroposofia l’arte riveste un ruolo fondamentale, tanto che si parla di arti antroposofiche.

Se Rudolf Steiner, il fondatore dell’Antroposofia, ha ritenuto che l’intera esperienza umana dovesse venire informata dalla conoscenza spirituale, tanto da influenzare anche la musica, il canto, la poesia, la scultura, la pittura, la danza (euritmia) e persino l’architettura (architettura organica), allora dobbiamo domandarci:

Come possiamo pensare che un’attività umana, oggi così diffusa, come la programmazione informatica, che abbiamo visto essere così legata anche al concetto di creatività, possa rimanere esclusa dalla visione antroposofica?

Come cultori dell’Antroposofia, è forse arrivato il momento di rivolgere la nostra attenzione anche al mondo legato alla programmazione informatica, senza preconcetti, forti anche del fatto che il software è la parte immateriale di questo mondo, la parte più legata al pensiero immaginativo.

“L’informazione è informazione, non materia o energia.” Norbert Wiener

Per cominciare a farlo, si pensi al fenomeno dell’arte digitale o, per esempio, al linguaggio di programmazione Processing che, tra le altre cose, permette di sviluppare opere d’arte generativa.

“[…] ci stiamo avvicinando sempre più all’epoca della composizione consapevole, razionale. Presto il pittore sarà orgoglioso di spiegare che le sue opere sono costruite.” Wassily Kandinsky, Lo spirituale nell’arte

Fare coding antroposoficamente

Se vogliamo provare a mettere in dialogo il mondo dell’Antroposofia con quello del coding occorre forse immaginare nuove applicazioni che siano capaci di toccare lo spirito di chi le usa, e magari spingersi oltre dando vita ad un nuovo paradigma di programmazione che permetta allo spirito dell’autore (il programmatore) di esprimersi.

Un primo possibile passo…

Chi si avvicina all’Antroposofia scopre molto presto l’importanza che in essa riveste il mondo dei colori e delle figure geometriche, unito al mondo dei concetti e delle idee.

Forse un primo passo potrebbe proprio essere quello di riflettere su come utilizzare i colori e le figure geometriche per creare software antroposofico.

Un piccolo primo esempio: come generare immagini in stile Mondrian.

Ma poi, provando a spingersi oltre, occorrerebbe esplorare la possibilità di creare un nuovo paradigma di programmazione.

Mi rendo conto che tutto questo possa apparire come estremamente vago e privo di solide basi, ma si tratta per adesso soltanto di una intuizione su cui magari cominciare a riflettere e lavorare.