Parte introduttiva
Programmazione orientata agli oggetti (linguaggio OO+)
Cominciare a studiare un linguaggio di programmazione orientato agli oggetti implica il mettere da parte tutto ciò che è stato acquisito nello studio del C. "Dovrebbe ormai essere chiaro che il C è un linguaggio di tipo procedurale, questo significa che si sviluppa tutto all'interno di una funzione principale, il main(), (con si sviluppa si intende che parte e finisce) in cui delle strutture dati e delle funzioni (atte ad operare su di esse) evolveranno dando poi i risultati desiderati. In tutti i casi anche in Java (il linguaggio che vogliamo imparare), come abbiamo fatto in C, programmeremo su più file."
L'esigenza di ragionare ad oggetti è insita nella natura umana! Pensiamo per esempio ad un veicolo, questo è un oggetto che si muove; ma a sua volta un veicolo è composto da altri oggetti (oggetto motore, oggetto sportelli, oggetto ruote, ecc.).
Per cui visto che l'uomo ha per sua natura la predisposizione del ragionamento ad oggetti, si è pensato bene di intercalare questo metodo di ragionamento in un linguaggio di programmazione. Praticamente in questo linguaggio di programmazione si creano degli oggetti, tra loro indipendenti (e magari sufficienti), e li si inseriscono in un determinato contesto facendoli interagire tra loro.
Definizioni di classe, oggetto e metodo
Cominciamo con il dare le definizioni di: classe, oggetto e metodo. Una classe è un'astrazione di "cose" simili tra loro. Se per esempio consideriamo 5 persone (Mario, Marco, Francesca, Bruno, Luisa) ogni persona rappresenterà un oggetto ben preciso con caratteristiche uniche! Ma tutte e cinque sono delle persone, ecco allora il concetto di classe persona! La classe dunque è un "concetto" comune a più oggetti.
Pensiamo, per esempio, ad un veicolo; un veicolo è un oggetto che si muove, essendo questo un "concetto" generale il veicolo è una classe. Ma ogni veicolo a sua volta è poi identificato da un modello, da un colore, dalle dimensioni, dal numero di telaio, ecc. Ogni veicolo potrà avere un certo numero di marce, che funzioneranno in un certo range di n° di giri differente e consentiranno al motore di muoversi con diverse velocità; quindi di attivare comportamenti e azioni differenziati. Quanto è stato appena detto ha a che fare con i metodi.
Riassumendo
Il "concetto" unificante è la classe (esempio veicolo), una classe definisce l'insieme delle caratteristiche comuni a diverse "cose". Caratteristiche che possono essere strutturali, cioè gli attributi (nell'esempio del veicolo il n° di ruote, il colore, ecc.), e comportamentali, cioè i metodi / operazioni (nell'esempio del veicolo la velocità del suo movimento).
La singola "cosa", poi, rappresenta l'oggetto, o istanza di una classe. Per esempio l'Alfa Romeo Brera rappresenta l'istanza (oggetto) della classe veicolo!!! Ovviamente la classe rappresenta il "concetto unificante" di un insieme di cose e per questo motivo fisicamente non esiste, in quanto è solo un pensiero, un'immaginazione.
Per rendere più chiara questa affermazione pensiamo alla classe STUDENTE: STUDENTE in questo caso è il concetto; gli attributi di STUDENTE potranno essere n° di matricola, età, ecc. e i metodi saranno studiare, seguire_le_lezioni, ecc. Ma un'aula piena di studenti, quindi di cose che fisicamente esistono, rappresenta un insieme di oggetti, cioè di istanze della classe STUDENTE!
Per cui nella realtà esistono solo gli oggetti. Dovrebbe inoltre essere chiaro che in una classe, il metodo di quella classe può lavorare solo sugli attributi di quella classe e non sugli attributi di una classe differente (ad esempio, date le classi PERSONA e VEICOLO, il metodo cammina_in_autostrada può lavorare solo sugli attributi della classe VEICOLO).
A seconda degli autori o del contesto in cui ci troviamo i seguenti termini vengono usati come sinonimi, o con significati molto simili:
- Attributo, campo, proprietà
- Metodo, operazione, servizio
Gli attributi sono la definizione dei valori caratteristici di ogni singola istanza (in un certo senso potremmo dire che gli attributi sono delle variabili che ogni oggetto ha). Un metodo è una funzione che tutti gli oggetti possono svolgere.
Come è stato prima accennato il nostro scopo è quello di imparare il Java, tuttavia non ci addentreremo subito all'interno del linguaggio ma per concretizzare al meglio le idee introdotte, senza concentrarsi su una specifica tecnologia, introdurremo un linguaggio astratto (non esiste realmente), una sorta di pseudo - java che chiameremo OO+ (solo successivamente rivedremo i concetti principali, che qui introdurremo, applicati al JAVA).
Definire una classe in OO+
Cominciamo subito col vedere come è possibile definire una classe in OO+: A tal proposito esiste il costrutto class{}, quindi volendo definire una classe PERSONA, dobbiamo scrivere:
class PERSONA{}
A questo punto tutti gli attributi (ed eventuali metodi) dovranno essere scritti all'interno delle parentesi graffe, quindi volendo dichiarare la classe PERSONA, al cui interno si trovino gli attributi: nome, cognome e data di nascita, dobbiamo scrivere:
class PERSONA{string nome; string cognome; date data_di_nascita;}
Istanziare un oggetto
Stiamo attenti ad una cosa, in questo modo abbiamo solo definito il concetto di persona, ma fisicamente non ne abbiamo ancora creata nessuna, in un certo senso stiamo insegnando al nostro programma cosa sarà una persona, ma effettivamente ancora non gliene abbiamo fatta vedere nessuna!
A questo punto se volessimo effettivamente istanziare un oggetto dalla classe persona dobbiamo utilizzare l'operatore new(), tale operatore ogni volta che viene invocato crea un nuovo oggetto istanza di una determinata classe. La sintassi di questo operatore è la seguente:
PERSONA o = new PERSONA()
Vediamo di analizzare nel dettaglio quest'ultima riga: Quindi un oggetto prende forma solo con l'operatore new.
Praticamente il codice di un servizio viene compilato come una funzione in linguaggio procedurale, e all'interno della classe avremo solo un riferimento a tale codice!
Il costruttore
Dobbiamo adesso introdurre un nuovo concetto, il costruttore. Praticamente quando scriviamo la riga: C o = new C() 1. C o dichiara una variabile oggetto atta a memorizzare handle ad oggetti istanze di C 2. new è l'operatore di istanziazione che prepara l' "area di memoria" in cui memorizzare l'oggetto 3. C(), invece, è il costruttore della classe C che va ad inizializzare in modo opportuno "l'area di memoria" precedentemente creata da new. A questo punto il nuovo oggetto è stato creato ed inizializzato 4. = (operatore di assegnazione) memorizza l'handle dell'oggetto, creato in (3), nella variabile oggetto o
I costruttori vanno elencati tra i metodi offerti dagli oggetti di una classe; questi vengono richiamati dall'operatore new dopo che è stata creata l' "area di memoria"; hanno il compito di inizializzare gli attributi dell'oggetto, in modo che i loro valori iniziali siano quelli desiderati; possono avere parametri (spesso utilizzati per inizializzare gli attributi). Il costruttore dunque è una sorta di servizio, il cui nome è uguale a quello della classe. Lo possiamo immaginare in un certo senso come il servizio principale; perché il costruttore è colui che effettivamente costruisce la classe.
"L'unico rigore del costruttore è che deve avere lo stesso nome della classe, facendo attenzione alle lettere maiuscole e minuscole."
Nel linguaggio astratto che stiamo considerando (OO+) un costruttore è un metodo con lo stesso nome della classe, che non ha tipo di ritorno e può avere dei parametri in ingresso. Il costruttore che non ha parametri in ingresso è detto costruttore di default.
Costruttori multipli
Per una stessa classe è possibile avere più di un costruttore, l'importante è che siano differenti nel numero e/o tipo di parametri. A che ci possono servire più costruttori di una stessa classe? Immaginiamo di disporre della classe AUTO i cui parametri sono: colore, n° di porte, cilindrata, n° di telaio, ecc. con un costruttore specifico possiamo istanziare un oggetto delle classe auto e passargli i valori delle variabili, se invece utilizziamo un costruttore differente utilizzare variabili differenti.
Esempio: Il costruttore quindi serve ad inizializzare gli oggetti; in particolare serve ad inizializzare gli attributi di base dei nostri oggetti (con attributi di base si intende gli attributi che non dovranno più cambiare durante l'evoluzione del programma). Concentriamoci adesso sugli handle e cerchiamo di conoscerli un po' più da vicino.
"In un certo senso gli handle sono simili ai puntatori visti in C..." Consideriamo di aver dichiarato la classe PERSONA: Se invece, data la classe PERSONA:
Gestione degli handle
Consideriamo adesso quest'altra situazione, data sempre la classe persona: Un aspetto molto importante è che se ho un handle di tipo persona non posso, su di lui, istanziare un oggetto di tipo differente (questo per il discorso dell' ereditarietà che vedremo in seguito): Oltre al metodo costruttore, tra i servizi offerti da una classe esiste il metodo distruttore; questo ovviamente al contrario del costruttore serve distruggere gli oggetti (viene invocato con il comando dispose). Il distruttore della classe C è indicato come ~C().
Tuttavia abbiamo detto che esiste un garbage collector che ha il compito di deallocare tutti gli oggetti non referenziati da nessuna variabile oggetto; per questo motivo non ci occuperemo ulteriormente della deallocazione esplicita degli oggetti.
Abbiamo detto che all'interno di una classe ci sono sicuramente degli attributi ed eventualmente dei metodi. Ci chiediamo allora, come è possibile indicare l'attributo (o metodo) di una determinata classe? La risposta è: mediante la "dot notation", cioè con l'operatore punto: C.attr - C.metodo Per cui se o (oggetto) è un’istanza della classe C, allora o.foo() significa invocare il metodo sullo specifico oggetto e o.attr rappresenta il valore dell'attributo presentato dallo specifico oggetto: Quando viene invocato un metodo la loro esecuzione non avviene all'interno della classe. All'interno della classe, infatti, abbiamo solo un riferimento alla porzione di codice del metodo. Questo per far sì, in fase di run-time, di poter cambiare metodo senza distruggere l'intero oggetto (meccanismo di polimorfismo).
Metodi e attributi di una classe
Dovremmo ormai aver capito che all'interno di una classe ci sono metodi e attributi. Talvolta gli attributi possono essere utilizzati per rappresentare lo "stato" di un oggetto; a tal proposito è opportuno fare una distinzione tra stato mutabile e stato immutabile:
- Lo stato immutabile costituisce l'essenza dell'oggetto; non viene modificato durante il ciclo di vita dell'oggetto e spesso viene inizializzato nei costruttori (es. nome e cognome).
- Lo stato mutabile, invece, viene modificato durante il ciclo di vita dell'oggetto, attraverso l'invocazione dei metodi (es. età).
Per cui indipendentemente dal valore degli attributi ogni oggetto ha identità propria (immaginiamo due auto identiche ma con un numero diverso di chilometri percorsi). Abbiamo capito allora che la classe rappresenta la base di un programma Orientato ad Oggetti.
Anatomia di un linguaggio O.O.
Vediamo ora un po' di anatomia di un linguaggio O.O. Questi programmi si dividono in 2 parti fondamentali: nella prima parte trovano posto tutte le definizioni di tutte le classi dell'applicazione (in particolare ogni classe deve stare su un file, quindi in un programma con 10 classi sicuramente dobbiamo avere 10 file); nella seconda parte invece si allocano gli oggetti e si invocano i metodi su di essi, nel linguaggio OO+ (che stiamo considerando) questa seconda parte è rappresentata dalla funzione speciale main(), questa è la prima porzione di codice che va in esecuzione quando viene lanciato il programma.
Andiamo adesso ad analizzare un semplice programma OO+: Negli esempi visti fino a questo momento in alcuni stampavamo dei messaggi a video con la riga: system.println("......"); Con questa riga stiamo intendendo che esisterà sicuramente una classe di nome system il cui metodo è println. Questa classe, inoltre, non è stata né dichiarata né istanziata, ma è direttamente pronta all'uso!
Cioè, fino a questo momento abbiamo detto che per poter invocare un metodo di una classe dobbiamo:
- Dichiarare un handle di quella classe
- Istanziare un oggetto di quella classe
- ORA, possiamo invocare il metodo di quell'oggetto
Ma per quanto riguarda la system non abbiamo fatto nulla di tutto questo! Ci chiediamo allora: "Come è stato possibile invocare il metodo println della classe system senza aver istanziato nessun oggetto della classe system?".
Attributi e metodi di classe
Per rispondere a questa domanda dobbiamo introdurre i concetti riguardanti gli attributi e i metodi di classe. La cosa fondamentale da tenere presente è che la memoria si divide in 2 parti: heap (mucchio) e stack; Nello stack viene memorizzato tutto ciò di cui se ne conoscono le dimensioni a priori (puntatori, ecc.), nell'heap invece viene memorizzato tutto quello che è variabile (array dinamici, liste, ecc.).
Per cui quando, per esempio, dichiariamo un handle di una classe questo verrà memorizzato nello stack, quando creiamo l'oggetto invece, verrà creato nell'heap e lo possiamo gestire mediante l'handle che risiede nello stack!
Inoltre abbiamo detto che quando dichiariamo una classe non stiamo effettivamente riservando uno spazio in memoria, ma stiamo avvisando il compilatore su quanto spazio riservare ad eventuali oggetti di quella classe. Per questo motivo quando dichiariamo una classe non possiamo accedere ai metodi e agli attributi di quella classe, se non prima viene istanziato un oggetto di quella classe, perché in memoria non esiste ancora niente!
Tuttavia nei linguaggi O.O. esiste la possibilità di definire attributi e metodi "di classe". Cioè è possibile dichiarare una classe e riservare spazio in anticipo (nello stack) per alcuni (o tutti) atributi e/o metodi di quella classe. In questo modo quando andiamo a dichiarare una classe se sfruttiamo questa "potenzialità" ci è possibile accedere agli attributi e ai metodi di quella classe senza istanziare nessun oggetto.
Per cui quando in una classe riserviamo spazio in anticipo ad attributi e/o metodi, questi non sono associati a nessun oggetto di invocazione, ma esiste un'unica caratteristica condivisa da tutte le istanze di quella classe. Se invece nella dichiarazione della classe riserviamo spazio in anticipo per un metodo, il codice di quel metodo viene eseguito senza dover essere richiamato su un oggetto di invocazione.
Ma come possiamo, nel dichiarare un classe, riservare spazio in anticipo per i metodi e/o gli attributi? Nel linguaggio OO+ lo faremo anteponendo la parola global alle dichiarazioni (in Java la parola static).
Facciamo un esempio per chiarire le idee: Immaginiamo di avere dichiarato la classe PERSONA e di voler sapere quante istanze vengono create da quella classe; come possiamo fare? Ovviamente avremo bisogno di una variabile (numistanze), all'interno della classe persona, che venga incrementata di 1 ogni qualvolta richiamiamo il costruttore PERSONA().
Tuttavia se dichiariamo una variabile normale ogni volta che istanzieremo un nuovo oggetto di quella classe, la variabile numistanza verrà di volta in volta inizializzata, quindi se facciamo 10 istanze di persona la variabile varrà 1 e non 10! In quanto ogni volta che facciamo il new l'area di memoria è nuova, la variabile viene inizializzata a 0 e poi settata a 1. Cioè fondamentalmente avremo 10 variabili numistanze tutte uguali a 1, invece il nostro scopo è avere un'unica variabile numistanze (globale a tutte le istanze) = 10! La soluzione è quella di dichiarare tale variabile come globale!! In questo modo la variabile numistanza farà riferimento alla classe persona e verrà incrementata di 1 ogniqualvolta istanziamo un nuovo oggetto; cioè questo attributo è visto da tutte le istanze e se una istanza lo modifica anche la modifica sarà vista da tutti!
Procediamo allora così: Per cui con la seguente classe main(): Tornando quindi alla system abbiamo potuto utilizzare il metodo println senza istanziare nessun oggetto dalla system, perché sarà stato sicuramente definita prima come global!
Ricapitolando: ogniqualvolta avremo bisogno di definire un qualcosa (variabile o metodo) visibile a tutti gli oggetti della stessa classe, lo definiremo come global (static in Java).
Il concetto di "this"
Introduciamo ora un nuovo concetto, non meno importante: la parola riservata "this". Il "this" ci consente di fare riferimento allo stesso oggetto di invocazione. Cioè per esempio, se abbiamo la classe:
Visibilità
Passiamo adesso alla VISIBILITÀ: Questo è un concetto che esiste in ogni linguaggio di programmazione. Per ora faremo un discorso che vale in generale per i linguaggi OO; successivamente lo riprenderemo per il JAVA.
Nei linguaggi O.O. si possono stabilire regole di visibilità sulle caratteristiche: public, private, protected.
- Public: accessibile a tutti
- Private: accessibile solo all'interno della classe che lo dichiara (utilizzeremo il modificatore private per esempio quando gli attributi non devono essere modificati direttamente!).
- Protected: (Il significato è differente a seconda del linguaggio considerato, noi considereremo quello di JAVA) un campo è protetto quando è accessibile solo all'interno della classe che lo dichiar
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
-
Sistemi di elaborazione - sistemi informativi
-
Architetture Sistemi Elaborazione
-
Sistemi di elaborazione I - i sistemi informativi
-
Sviluppo dei sistemi di informazione: Appunti di Sistemi