Differenze tra le versioni di "Gli Oggetti e le Classi"
Riga 241: | Riga 241: | ||
''Solitamente questi sono preceduti da un trattino basso in modo che l'IDE li nasconda dalle opinioni delle persone normali.'' | ''Solitamente questi sono preceduti da un trattino basso in modo che l'IDE li nasconda dalle opinioni delle persone normali.'' | ||
+ | |||
+ | |||
+ | ==Un indice delle Classi di Gambas== | ||
+ | https://www.gambas.one/classes/ | ||
Versione delle 17:31, 27 dic 2021
In Gambas, il concetto e la logica degli Oggetti è stata fortemente implementata; infatti, nel linguaggio base esistono due categorie: Classe e Modulo.
Queste due categorie, nella realtà, hanno pochissime differenze, nel senso che in ogni caso sono basate sul concetto di Oggetto. [Nota 1][Nota 2]
Differenza fra Classe e Modulo
La differenza sostanziale tra queste due implementazioni, è che la Classe può possedere e pertanto fornire "Proprietà", "Metodi", "Eventi", "Variabili" e "Costanti", mentre il Modulo non può possedere propri "Eventi". Il Modulo non è altro che un contenitore di Funzioni e/o di Variabili e/o di Costanti, che vengono referenziate agendo sul Modulo stesso. Ciò significa che per richiamare e fruire di una risorsa del Modulo basterà utilizzare il nome identificatore del Modulo stesso. Così, se ad esempio il Modulo è stato chiamato dal programmatore "mio_modulo.module", e possiede un "Metodo", chiamato "Metododelmodulo()", per invocare detto "Metodo" nel codice basterà scrivere:
mio_modulo.Metododelmodulo()
Il concetto di base, però, è identico a quello della Classe, tranne per il fatto che di un Modulo, a differenza di una Classe, non possono essere creati dinamicamente "Oggetti": una volta stabilita la sua funzione all'interno di un'applicazione, e definiti i suoi "Metodi" interni, il Modulo rimane "statico " all'interno del programma. [Nota 3] Il discorso è molto simile, se non completamente analogo, alle Classi "statiche" contenute nelle librerie di base di Gambas, come ad esempio "gb".
Diversamente, una Classe, perché possa fornire concretamente le risorse ("Proprietà", "Metodi", "Eventi", "Variabili" e "Costanti") possedute, deve essere "istanziata", ossia è necessario che vengano "creati" (con la parola-chiave "NEW") dei singoli Oggetti della Classe.
Dim oggetto As NEW CLASSE
Un Oggetto in Gambas è una struttura di dati che fornisce concretamente Proprietà, Metodi ed Eventi, previsti in astratto dalla "Classe" alla quale esso appartiene.
Di una Classe è possibile creare anche più Oggetti individuali.
Pertanto, a differenza del Modulo la Classe - purché non sia di tipo Statico - non può essere utilizzata in modo diretto nel codice, ma deve essere utilizzata "mediante" suoi Oggetti: deve essere "istanziata".
All'interno di un progetto, si ha la possibilità di creare, sia nuove Classi, che nuovi Moduli; il nome dei file assumono l'estensione ".module" per i Moduli, e ".class" per le Classi. Discorso leggermente diverso per gli oggetti grafici (esempio le Form), che creano un'ulteriore file ".form", contenente la definizione delle caratteristiche grafiche dell'oggetto e degli elementi inclusi in esso.
A differenza di una classe, un Modulo (come già detto) non può essere creato (esiste, e basta!); per cui non ha alcun Costruttore, e di conseguenza nessun Distruttore (vedi _new() e _free() in Metodi nascosti) e non può sollevare Eventi attraverso l'istruzione "Event ". [Nota 4]
Esempi:
'Esempio 1: creazione di una classe dinamica DIM hWin AS NEW Window()
'Esempio 2: referenziazione di una classe dinamica DIM hWin2 AS Window hWin2 = hWin
'Esempio 3: utilizzo di una funzione modulo DIM value AS Float = ModUtil.CalcoloPerc(10)
Negli esempi sono mostrati alcuni modi di utilizzo, sia riguardo le classi, sia riguardo un modulo:
a) Nell'esempio 1 viene creato un nuovo oggetto di tipo Window, e referenziato dalla variabile hWin.
b) Nell'esempio 2 viene definita una variabile di tipo Window, che viene poi associata all'oggetto precedente hWin; in questo caso non viene creato un nuovo oggetto, ma solo un riferimento a quello già esistente. In questo caso, l'eliminazione della variabile hWin2 non distruggerà l'oggetto hWin, ma toglierà solo il suo riferimento; in ogni caso la variabile precedente hWin, ovvero il vero riferimento all'oggetto, manterrà sempre l'oggetto in memoria. L'eliminazione dell'oggetto dovrà essere eseguita agendo sul proprio metodo ".Delete()" (se presente), o eliminando la stessa variabile hWin. E' da notare che, se viene cancellata la variabile originale hWin, e viene mantenuta hWin2, l'oggetto resta comunque in memoria, dato che esiste comunque un suo riferimento valido. Questo giochetto può essere molto utile in alcuni casi, ma è necessario fare molta attenzione a non lasciare appesi riferimenti inutili.
c) Nell'esempio 3 viene utilizzata una funzione interna al modulo ModUtil: CalcoloPerc(). A parte la sua funzione specifica (è solo un esempio), il concetto è visibilmente diverso dall'utilizzo di una classe. L'uso della funzione CalcoloPerc() deve avvenire passando per il riferimento al modulo ModUtil, ma l'utilizzo è immediato, come anche il valore di ritorno.
Come per Classi, i Moduli possono contenere variabili interne, statiche (STATIC), private (PRIVATE) e/o pubbliche (PUBLIC), e anche costanti (CONST). Dipedentemente dalla loro dichiarazione, l'uso è identico a quello di una proprietà di classe, tranne per il fatto che la proprietà (o variabile) non viene creata, ma esiste nello stesso ambito del modulo.
' Gambas module file . '*** Modulo ModUtil *** . PUBLIC FUNCTION CalcoloPerc(value AS Float) AS Float ... ... ... END
Nell'esempio viene mostrato come può apparire il codice del modulo ModUtil, e il suo metodo (o funzione) interno.
' Gambas class file . '*** Classe MyWindow *** . PUBLIC SUB Form_Open() ... ... ... END
In quest'altro esempio viene mostrato il codice di una Classe Window.
Come si può notare, la struttura dei due file è molto simile (se non addirittura uguale); Gambas da parte sua, inserisce automaticamente un marcatore ad inizio file, ad evidenziare il tipo di file che si sta trattando. Ricordo che la modifica di questa nota non è permessa, e in ogni caso Gambas la reinserisce automaticamente.
Copia e clone di un Oggetto
Per gli Oggetti, una copia equivale a copiare l'indirizzo in memoria dell'oggetto, e quindi il suo riferimento. Ogni modifica apportata su qualsiasi variabile che contenga il solo indirizzo, modifica di conseguenza l'Oggetto.
Per avere invece un clone, ossia duplicato dell'Oggetto, è necessario usare il Metodo - laddove la Classe lo disponga - ".Copy()", che crea un clone dell'Oggetto, ma in altra zona di memoria.
Non tutti gli oggetti base hanno un Metodo ".Copy()"; in tal caso caso è necessario implementarlo, creando un nuovo Oggetto e copiando le proprietà dell'originale sul nuovo.
Particolari parole-chiave della Classe: ME - SUPER - INHERITS
Come già descritto, le Classi differiscono dai moduli, principalmente per il fatto che possono e devono essere creati e istanziati. Oltre a questo, hanno alcune particolarità, che ne semplificano l'uso, e oltre tutto devono obbligatoriamente possedere, per poter gestire un oggetto in maniera umana. A parte i propri metodi e le loro proprietà, che dipendono dal tipo di oggetto e dal tipo di funzione che deve svolgere, esistono delle parole chiave, con lo scopo di fornire elementi utili al loro utilizzo.
ME - Riferimento a se stesso
La parola chiave [Nota 5] ME è messa a disposizione per poter puntare all'oggetto, all'interno di se stesso; cosa che altrimenti non sarebbe fattibile. Come per i moduli, che si referenziano esternamente, dichiarando il nome del modulo stesso, per le classi, all'interno dei loro metodi, si può avere un riferimento allo stesso oggetto con ME.
' Gambas class file . '*** Classe Form *** . PUBLIC SUB Form_Open() ... ME.Caption = "Nome della Form" ... END
Nell'esempio viene impostato il testo che comparirà sulla testata della Form; come si può notare, il riferimento alla Form (ovverro a se stessa), viene fatto da ME. Il punto che segue indica al compilatore di Gambas, che il nome che seguirà sarà un riferimento ad una proprietà o un metodo interno alla classe (in questo caso Caption).
' Gambas class file . '*** Classe Form *** . PUBLIC SUB Form_Open() ... WITH ME ... .Caption = "Nome della Form" .X = 0 .Y = 0 ... END WITH ... END
La sintassi di quest'altro esempio è la stessa, la differenza stà nel fatto di aver utilizzato la sintassi WITH...END WITH. Questo tipo di sintassi permette di stabilire a priori che si vogliono modificare solo elementi della classe, per cui non c'è più bisogno della parola ME, per cui i riferimenti verranno fatti iniziando i nomi delle proprietà di classe con il punto. E' sottinteso che questa logica è valida anche per i metodi, non solo per le proprietà:
' Gambas class file . '*** Classe Form *** . PUBLIC SUB Form_Open() ... WITH ME ... .Caption = "Nome della Form" .X = 0 .Y = 0 ... .Init() ... END WITH ... END ... ... ... PUBLIC SUB Init() ... END
Come si può notare, all'interno di WITH, è presente anche la chiamata al metodo Init(), che appartiene alla stessa classe.
SUPER - Riferimento alla classe superiore
Un oggetto (o classe) può essere derivata da un oggetto (o classe) superiore; in questo caso la nuova classe assume tutte le caratteristiche PUBLIC della classe padre, e la possibilità di espanderne le proprietà e/o specializzarne la funzione. Un esempio è la classe CheckBox, che deriva dalla classe Control.
' Gambas class file . '*** Classe MyWindow *** . INHERITS Window . PUBLIC SUB Window_Open() ... WITH ME ... .Caption = "Nome della Window" .X = 0 .Y = 0 ... END WITH ... END
Nell'esempio è mostrato il codice della classe MyWindow, che deriva dalla classe base Window di Gambas. La nuova classe assume tutte le caratteristiche PUBLIC della classe Window, comprese le proprietà, i metodi e i gestori di evento. A questo punto non è necessario riscriverne le proprietà, dato che sono già impostate a livello di classe padre; al più possiamo aggiungerne di altre, specializzando la funzione di questo nuovo oggetto. In realtà è anche possibile sovrascrivere una specifica funzionalità, rispetto alla classe padre; in questo caso è sufficiente ricreare il metodo, scrivendoci dentro il codice desiderato. Ma come facciamo a interagire con la classe padre? Semplice, in questo caso ci viene in aiuto un'altra parola chiave: SUPER. Ogni qualvolta entriamo in un metodo, che abbiamo sovrascritto per nostra necessità, possiamo chiamare il metodo della classe padre:
' Gambas class file . '*** Classe MyWindow *** . INHERITS Window . PUBLIC SUB Resize() ... SUPER.Resize() ... END
Nell'esempio viene utilizzato il metodo .Resize, nel quale abbiamo scritto codice adatto ai nostri scopi, e poi eseguiamo anche la stessa funzione nella classe padre, che potrebbe essere necessaria, in questo caso, a risolvere e attuare eventuali aggiustamenti alla Window.
INHERITS
Nello stesso esempio è presente anche un'altra parola chiave: INHERITS; ma questo è descritto in modo esplicativo in Inherits.
Metodi nascosti
Per ogni oggetto, sia che venga creato da noi, sia che faccia parte delle librerie standard di Gambas, esistono dei metodi, nascosti, che possono essere utilizzati per condizionare la creazione, o meno, dell'oggetto stesso. Questi metodi sono descritti in Metodi nascosti.
Classi virtuali
Tobias Boege, membro della Mailing list ufficiale di Gambas, descrive quanto segue:
esistono due tipi di classi virtuali: classi "native" e classi "virtuali" di Gambas.
E' possibile distinguerle approssimativamente dal Componente da cui provengono o dal primo carattere nel loro nome;
* un punto iniziale significa nativo (ad es. .Menu.Children dal Componente nativo gb.qt4 o .Watch.Events da gb.inotify);
* un trattino basso che significa che è scritto in Gambas (ad es. _MapTile in gb.map).
Questa denominazione non è un requisito ma una convenzione.
Una classe virtuale ha lo scopo di fornire un'interfaccia alternativa a una parte di un oggetto. Nei componenti nativi, una proprietà virtuale viene spesso implementata restituendo lo stesso oggetto esatto ma dicendo all'interprete di "eseguirne il cast" in un'altra classe, in modo da disporre di un set di Metodi più specializzato. Molti componenti si basano sul presupposto che possono semplicemente inserire alcuni dati in un campo nella struttura dati sottostante dell'oggetto e restituire se stessi per implementare una proprietà virtuale e che i dati non verranno toccati fino a quando l'oggetto virtuale non viene eseguito.
L'accessorio di proprietà di solito imposta una sorta di flag o registra alcuni parametri di accesso come lo slot delle impostazioni in _Settings_Keys._get () in modo che i metodi più specializzati sappiano dove si vuole che funzionino.
Si può vedere che con il nome delle classi virtuali native: .Menu.Children è una classe virtuale per la gestione della proprietà .Children di un oggetto Menu e .Watch.Events fa lo stesso per .Events di un Watch.
In realtà, le classi virtuali di Gambas (quelle che non sono native) sono solo normali classi. La "fusione di parte di un oggetto in una classe diversa" è una funzionalità strettamente nativa. Probabilmente non dovrebbero nemmeno essere chiamati "virtuali" ma sono spesso usati per lo stesso scopo. Il carattere di sottolineatura principale viene generalmente utilizzato per nascondere le classi, ad es. al popup di completamento automatico IDE. Si potrebbe teoricamente archiviare questi oggetti in un array, ma neanche questo è sicuro, a seconda di quali ipotesi la Classe faccia durante la sua vita.
Ora è possibile vedere il problema: gli oggetti virtuali in realtà non esistono. Sono solo una vista volatile su una parte specifica di un oggetto. Quindi non è possibile porli in un array. Le Classi virtuali possono essere dichiarate nei Componenti nativi, ma non sono creabili e hanno sempre la dimensione zero.
Una Proprietà o un Metodo virtuale consente di eseguire del codice nell'implementazione della proprietà o del metodo, che di solito registra solo alcuni dati nella memoria dell'oggetto e quindi restituisce l'oggetto stesso tramite RETURN_SELF (GB_PROPERTY_SELF è una scorciatoia per restituire se stessi e non fare nulla altro), ma l'interprete considera il valore restituito (sempre lo stesso oggetto) non come del suo vero tipo ma del tipo restituito indicato nella dichiarazione di proprietà.
Improvvisamente, lo stesso oggetto assume un'interfaccia completamente diversa, nuovi metodi, nuove proprietà. Ma è la stessa regione in memoria, quindi le implementazioni di queste nuove proprietà e metodi possono accedere all'oggetto originale. Viene normalmente utilizzato per fornire una vista specializzata sull'oggetto.
Tutto ciò implica, tra l'altro, che non è possibile archiviare oggetti virtuali in memoria, poiché sono solo viste temporanee su un altro oggetto. E l'uso di oggetti virtuali è intrinsecamente pericoloso per i thread poiché, a seconda della rispettiva implementazione, nel processo potrebbe esserci un solo oggetto virtuale per un determinato oggetto reale in qualsiasi momento.
Non è possibile implementare classi virtuali in Gambas. Lo si può fare solo usando l'API C/C ++ nativa dell'interprete, che ha la magia di cambiare un BLOB di descrizione dell'interfaccia con un altro. Tutto ciò che viene scritto in Gambas è una vera classe in cui non si può staccare la memoria dall'interfaccia.
Le classi virtuali sono in realtà solo un trucco nell'interprete per "eseguire il cast parametrico" di un oggetto in un'altra classe per non inquinare, per esempio, l'interfaccia diretta della classe Array con metodi e proprietà correlati ai suoi limiti, poiché quelli possono essere nascosti in un vista virtuale sull'array.
Le classi virtuali consentono di fare ciò:
1) in modo non dispendioso (perché tutto ciò che deve essere fatto è sostituire il puntatore al tipo di un oggetto);
2) senza dover creare nuovi oggetti e quindi creare interfacce della classe originale (semi-) pubblico in modo che questi nuovi oggetti possano accedervi.
Il solito modo di emulare questo in Gambas è:
- creare Classi reali per le viste virtuali, come _Array_Bounds con un Costruttore di _new (RealObject As Array);
- Array.Bounds restituisce una nuova istanza di questo _Array_Bounds e passa il riferimento "Me" al Costruttore;
- la classe Array deve rendere pubblici alcuni interni in modo che _Array_Bounds possa accedervi, essendo ora un oggetto diverso.
Solitamente questi sono preceduti da un trattino basso in modo che l'IDE li nasconda dalle opinioni delle persone normali.
Un indice delle Classi di Gambas
https://www.gambas.one/classes/
Note
[1] Bisogna ricordare che un Oggetto non è altro che una Classe istanziata.
oggetto = New Classe
laddove la variabile oggetto è appunto un Oggetto.
Con parola-chiave New viene creato un Oggetto, appartenente ad una specifica Classe, allocando automaticamente la necessaria quantità di memoria richiesta per esso e che così rappresenta la struttura interna dell'Oggetto medesimo.
Infatti, gli "Oggetti" in Gambas sono costituiti da Strutture e Sub-Strutture, scritte in C, che ovviamente occupano memoria.
E' il caso di sottolineare che è un errore l'uso - come argomento di una funzione - di una variabile di un Oggetto che non sia stato creato con la parola New.
La variabile che funge da Puntatore a un Oggetto sovente è definita "Handle " (maniglia), che rappresenta "una variabile associata a un oggetto complesso, che lo identifica". La maniglia è la "protuberanza", con la quale si interagisce con l'Oggetto. In Gambas, qualsiasi Oggetto (Form, o altro) è dunque in sostanza un "Handle ".
Questo "handle", questa variabile che rappresenta l'Oggetto di una Classe, dunque individua, indica e in certo senso fornisce al programmatore la prima cella di memoria della Struttura principale che costituisce l'Oggetto medesimo.
Questa prima cella di memoria, ove inizia la memoria riservata dal sistema per l'esistenza e la funzionalità dell'Oggetto di una Classe, possiede ovviamente un Indirizzo.
La variabile di un Oggetto, puntando all'area di memoria riservata all'Oggetto, punta dunque a quell'Indirizzo di memoria, si riferisce esplicitamente a quella cella di memoria; anzi esso contiene come valore proprio l'indirizzo di memoria di quella cella.
In Gambas gli Oggetti sono entità individuali concrete di un insieme più ampio, di un Universale che li trascende, chiamato Classe, e che rappresenta per essi un Modello, una Categoria assoluta delle loro caratteristiche (Proprietà, Metodi, Eventi, Variabili e Costanti) possedute che li distinguono dagli Oggetti appartenenti ad altre Classi.
Pertanto gli Oggetti, singolarità di una Classe, per esistere devono, come ogni entità concreta immanente, essere creati.
Una Classe concettualmente è ciò che è comune a più realtà individuali e concrete: gli Oggetti, accomunati tutti dalla circostanza di possedere e di fornire le risorse della Classe, alla quale essi appartengono. La Classe, dunque, è un "insieme" che raggruppa molti elementi individuali - gli Oggetti - che posseggono caratteristiche comuni. Tali caratteristiche in Gambas possono essere rappresentate dalle seguenti risorse:
- Proprietà (capacità dell'Oggetto di assumere una qualità);
- Metodi (capacità dell'Oggetto di compiere un'operazione);
- Eventi (capacità dell'Oggetto di far accadere qualcosa in caso di una sollecitazione);
- Variabili;
- Costanti.
La "Classe", dunque, è un sistema organico di risorse tra loro coerenti per la realizzazione di una o più finalità.
La Classe, come già detto, è il modello contenente specifiche caratteristiche (Metodi, Proprietà, Eventi, Variabili e Costanti), al quale fanno riferimento singoli elementi: gli Oggetti. In tal senso gli Oggetti appartengono ad una Classe, ossia sono la rappresentazione concreta delle caratteristiche e delle potenzialità di una Classe. Essi sono gli elementi capaci di fornire concretamente al programmatore ed all'utente le funzionalità della Classe, alla quale essi appartengono. Infatti, la Classe in sé non può essere utilizzata sic et simpliciter all'interno di un codice (ad eccezione delle Classi Statiche), giacché essa solo in astratto possiede alcune specifiche funzionalità. Tali capacità possono essere concretamente utilizzate solo attraverso elementi individuali, gli Oggetti, che alla Classe (astratta) fanno riferimento.
La Classe specifica, dunque, le sue caratteristiche e funzionalità, le quali potranno essere utilizzate e gestite mediante gli Oggetti. In particolare la Proprietà di un Oggetto è un'informazione definibile come attributo dell'Oggetto medesimo.
Tali funzionalità e caratteristiche (Metodi, Proprietà, Eventi, Variabili e Costanti) di una Classe sono tra loro coerenti ed organici alle specificità ed alle potenzialità della Classe, in modo tale che la Classe possa svolgere i compiti, per i quali fu creata.
La Classe è l'elemento astratto, nella quale sono definite determinate funzionalità, compiti e caratteristiche (Metodi, Proprietà, Eventi, Variabili e Costanti); mentre l'Oggetto è l'elemento concreto, virtualmente materiale, utilizzabile nel codice, e che rende possibile l'uso delle potenzialità della Classe, alla quale esso appartiene. In sostanza la Classe è utilizzabile solo attraverso gli Oggetti che ad essa si riferiscono.
La dinamicità della Classe è dovuta anche a questa sua capacità e necessità di esteriorizzarsi, di concretizzarsi in una possibile molteplicità di elementi (gli Oggetti) che ad essa si riferiscono, e che la rendono così effettivamente utilizzabile nel codice e, dunque, nel programma.
Per comprendere a pieno il concetto di "istanza" di una Classe, è opportuno comprendere l'etimo della parola, che proveniendo dal latino "in + sto", vuol significare "stare in", e quindi "stare nel (mondo)", "starci", "esistere nella (realtà)". Una Classe in quanto modello non esiste nel/sul mondo, ossia concretamente, è semplicemente un predicato astratto, un paradigma. Se la Classe si fa concreta, si in-stanzia, ossia si in-pone nella realtà concreta del programma, diventa gestibile in modo effettivo, virtualmente tangibile: diventa insomma un "Oggetto". L'Oggetto si pone, sta nel mondo del programma informatico con una sua individualità, definita e condizionata da funzionalità e caratteristiche che rimandano necessariamente alla sua Classe di appartenenza.
Per accedere all'uso delle risorse (Metodi, Proprietà, Eventi, Variabili e Costanti) di una Classe si adopererà l'operatore "." che distingue la variabile - identificatrice dell'Oggetto costruito - dalla sua risorsa, specificandone nello stesso tempo tipo e appartenenza:
nome_variabile.nome_risorsa
[2] Vedere anche le seguenti discussioni sul forum:
- https://www.gambas-it.org/smf/index.php?topic=933.0
- https://www.gambas-it.org/smf/index.php?topic=242
[3] Minisini, rispondendo nella Mailing List ufficiale di Gambas, ha così brevemente definito il Modulo:
"A module is just a class whose all methods and properties are static. It's a thing coming from Visual Basic. It's like a class, except everything is static. It's static, that means it cannot be instantiated."
Va ricordato che a una Classe statica o a un Modulo (che è di per sé statico) si può accedere senza bisogno di creare un'istanza della Classe o del Modulo.
[4] Un Modulo non può essere istanziato, non può creare il proprio Osservatore, né può sollevare direttamente un Evento mediante l'istruzione "Event". Per tanto un Modulo può solo “partecipare” agli Eventi pubblici di altre Classi.
[5] La Parola Chiave è una parola riservata del linguaggio di programmazione, che di norma non può essere utilizzata diversamente da ciò per cui è stata concepita.
Per esempio nella riga:
Dim variabile As Integer
la parola "Dim" è una parola chiave, mentre non lo è la parola "variabile".
In vero, però, una parola chiave può essere utilizzata, come identificativa di una variabile, purché posta fra due parentesi graffe:
Dim {Dim} As Integer {Dim} = 100000 Print {Dim}