Vediamo se riesco a insegnare la OOP in UN POST.
Supponiamo che chi legge conosca GIA' la programmazione procedurale ed i concetti base.
Programmazione Procedurale:
- tipi "primitivi":
- intero (int),
- float,
- boolean (vero/falso).
- ... C'e' ne sono altri, ma manteniamoci al minimo sindacale
- tipi "strutturati": se T e' un tipo (primitivo O strutturato), abbiamo
- T[n] (array): uno scatolone contenente tante scatollette, identificate da un NUMERO (0,1,2,..) ogn'una contenente SEMPRE lo stesso TIPO di valore.
Ad esempio int[5] e' un vettore di 5 interi, float[3][5] e' un vettore BIDIMENSIONALE/MATRICE di numeri con la virgola - T1 x T2 x ... (prodotto cartesiano/struttura): e' uno scatolone contenente tante scatolette, identificate da un NOME (ad esempio 'id', 'x' e 'y'), dove ogni scatoletta puo' contenere un tipo di valore diverso. NESSUNO limita il fatto che scatolette diverse possano avere lo stesso tipo di valore.
Ad esempio struct { int id, float x, float y }: rappresenta un punto in 2 dimensioni, con un identificativo ("id")
- funzioni/procedure: una volta, ed in Pascal questo c'e' ancora (o ci dovrebbe ancora essere) SE avevi una "funzione" che non deve ritornare nulla, la chiamavi "procedura". MA nel 99.9% del funzionamento, funzioni e procedure sono ESATTAMENTE la stessa cosa.
In C, una "procedura e' una funzione che ritorna "void", cioe' "niente".
Ma che cosa e' una "funzione"?
e' un pezzetto di codice che ha un NOME, un certo numero di PARAMETRI in ingresso e ritorna un "unico" valore come RISULTATO (che puo' essere anche il valore "speciale" "niente"). Ad esempio:- float sin(float x) la funzione trigonometrica "seno",
- int sum(int[] array): la funzione che ritorna la somma di tutti i valori interi all'interno dell'array
.
Ora supponiamo di avere un "ostello per animali" in cui teniamo cani e gatti. Ci servono 3 tipi strutturati:
- 'Ostello': la struttura contenente l'array di cani e di gatti
- 'Cane': la struttura ceh descrive un cane
- 'Gatto': la struttura che descrive un gatto.
.
Per un motivo 'convenzioni nella scrittura del codice' in MOLTI linguaggi di programmazione, i nomi delle strutture dati iniziano con la lettera maiuscola.
Ora sevono un po' di funzioniche per gestire il tutto:
- Cane creaCane(...): crea un'istanza della struttura Cane con le info sul cane (nome, allergie, proprietario, che cosa mangia)
- Gatto creaGatt(...): come sopra ma per un gatto
- Ostello creaOstello(): come sompra ma per l'ostello
- aggiungiCane(Ostello ostello, Cane cane): aggiungi un cane all'ostello
- rimuoviCane(Ostello ostello, Cane cane): rimuovo un cane all'ostello
- aggiungiGatto(Ostello ostello, Gatto gatto)
- rimuoviGatto(Ostello ostello Gatto gatto)
.
Come noti, ci sono UN SACCO di diplicazioni: cose MOOOLTO simili ma che DEVONO essere mantenute diverse.
Per esempio: all'ostello non interessa sapere se deve accettare un cane (dal nome Ettore) o un gatto (dal nome SIlvestro): li accetta entrambi, e per entrabi fa ESATTEMENTE le stesse operazioni di registrazione, scelta della gabbia, ricordarsi di dargli da mangiare ad intervalli regolari ...
Ci sono solo PICCOLE differenze sul fatto che uno sia un cane el'altro un gatto. Ma sono poche.
Altro problema: SUPPONIAMO che l'ostello decida di accettare ANCHE Pantere (in particolare di colore Rosa, pensavo ad Uccellini Gialli dal nome Titti, ma Pantere Rosa mi suona meglio ;-))
Sai che scocciatura dove implementare TUTTA una serie di funzionalita' che sono il copia/incolla al 90% di quello che e' gia' stato scritto?
.
E qui entra in gioco la OOP (Object Oriente Programming).
.
.
Programmazione ad Oggetti 1: EREDITARIETA'
Invece di avere Cane e Gatto (e Pantera :-)) come strutture dati diverse, le componiamo in questo modo
- Animale: una struttura dati che contiene TUTTE le informazioni comuni tra cani, gatti e pantere (rosa)
- Cane extends Animale: la struttura dati che EREDITA tutti i campi di Animale e che aggiunge quello suoi specifici. Ad esempio il tipo di osso che gli piace masticare
- Gatto extends Animale: come sopra. Ad esempio quale animale odia (Titti? :-))
- Pantera extends Animale: come sopra, con le sue specifiche caratteristiche (la marca del fucile che predilige quando deve sparare a qualcuno ;-))
- qui' servira aggiungere una nuova funzione "Pantera creaPantera(...)", ma ci sta', essendo un oggetto totalmente nuovo
.
Gia' in questo modo possiamo semplificare le funzionalita' associate ad Ostello, perche' invece di avere: aggiungiCane, aggiungiGatto, aggiungiPantera, rimuoviCane, ..., bastano
- aggiungiAnimale(Ostello ostello, Animle animale)
- rimuoviAnimale(Ostello ostello, Animale animale)
.
SI e' passati da 6 a 2.
Non solo, ma diventa "banale" aggiungere altri tipi di "animali", che ne so, una "Puzzola" (dal nome "Pepé Le Pew" ;-)) o "Canarino" (dal nome "Titti" :-) ), ecc.
.
.
Programmazione ad Oggetti 2: POLIMORFISMO
Al punto 1) abbiamo creato 4 strutture dati:
- Animale
- Cane extends Animale
- Gatto extends Animale
- Pantera extends Animale
.
Ora, ovviamente ogni 'animale' deve mangiare e ci serve sapere CHE COSA puo' mangiare. E' ovvio che per 'Animale', essendo un animale 'generico' non lo si puo' sapere apriori, MA per 'Cane' sappiamo che mangia "carne", il "Gatto" mangia "pesce" e "Pantera" (sopprattuto se Rosa) mangia "PizzaLuigiXII" (12 MILA dollati al pezzo ;-)).
Diciamo di assegnare:
- il valore intero 1 per "carne"
- il valore intero 2 per "pesce"
- il valore intero 12000 per "PizzaLuigiXII"
.
Ora ci serve una "funzione" che dato un "Animale" mi ritoni "che cosa mangia" in forma di un numeri intero, che nel nostro caso sara' 1, 2, o 12000.
La funzione DEVE essere:
- int cheCosaMangia(Animale animale)
.
E' OVVIO che questo non basta, MA ci servono anche una serie di "funzioni" che hanno lo STESSO NOME, ogn'una per un diverso tipo di animale:
- int cheCosaMangia(Cane cane) { return 1; }
- int cheCosaMangia(Gatto gatto) { return 2; }
- int cheCosaMangia(Pantera pantera) { return 12000; }
,
E QUI entra in gioco la vera "potenza" della programmazione OOP: E' RESPONSABILITA' dell'implementazione del linguaggi di programmazione TROVARE UN MODO per decidere SE usare 1), 2) o 3) QUANDO nel codice viene chiamato
- int cheCosaMangia(animale)
.
SENZA SAPERE che animale e' ESATTAMENTE ma che, DI VOLTA IN VOLTA, l'animale passato puo' essere un cane, un gatto o una pantera "rosa".
Nota per i puristi
questa sintassi e' stata proposta da Stroustrup:
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4474.pdf
Inoltre e' implementata, in versione generalizzata, in Julia.
.
.
Programmazione ad Oggetti 3: CLASSI & METODI
Con quanto detto nei punti 1 e 2 abbiamo TUTTO quello che serve per passare alla Programmazione Orientata agli Oggetti (OOP):
- una classe non e' altro che una struttura che non ha SOLO i campi contenenti i dati, ma anche APPICCICATE le funzioni per manipolarla.
Nell'esempio di cui sopra,- Cane ha appiccicato int cheCosaMangia(Cane cane),
- Gatto ha appiccicatto int cheCosaMangia(Gatto gatto),
- Pantera ha appiccicatto int cheCosaMangia(Pantera pantera),
- Animale ha appiccicato int cheCosaMangia(Animale animale)
- un metodo non e' altro che le "funzioni" usate sopra ma scritte in modo leggermente diverso:
- invece di int cheCosaMangia(Cane cane),
- si scrive int Cane.cheCosaMangia():
come si puo' notare dalla sitassi, e' SPARITO il PRIMO parametro, perche' sara' responsabilita' del compilatore aggiungerlo AUTOMATICAMENTE.
.
Quindi, un ipotetico pezzetto di codice in STILE PROCEDURALE:
Animale animale = creaPantera("Rosa");
print(cheCosaMangia(animale))
in stile OOP diventerebbe
Animale animale = creaPantera("Rosa");
print(animale.cheCosaMangia());
.
Conclusioni
E' OVVIO che spiegare la OOP in un SINGOLO post e' alquanto arduo (ed e' il primo tentativo, magari con ulteriori raffinamenti si puo' fare di meglio)
Ed e' anche ovvio che sono stati descritti SOLO i due concetti piu' importanti.
Il C++ e' infinitamente piu' elaborato, con un'infinita' di funzionalita' aggiuntive.
Ma, insomma. ;-)