News
Articoli
Download
Tips & Tricks
Link
Guestbook
 
 
 
 
Copyright© 2004
Ivanov Dmitrij
Evghegnevich
e-mail

Risoluzione consigliata
1024 x 768
 
Ti piace il sito?
Votalo con un clic sul
banner qui sotto ^_^
 
   
 
 
 
 
 
Introduzione alle classi
 
E' ormai da tempo che l'avvento della programmazione orientata agli oggetti ha fatto irruzione nel mondo dell'industria informatica e molti programmatori, abituati al vecchio stile, si sono dovuti arrangiare per sfruttare questo importante paradigma. I giovani sviluppatori, se non opportunamente preparati, fanno fatica ad apprendere il pensiero che sta alla base ed i programmatori abituati al vecchio stile hanno sudato assai per essere al passo con i tempi.

In questo appuntamento affronteremo insieme alcuni degli arcani segreti che la programmazione orientata agli oggetti, detta più brevemente OO (Object Oriented), vela ai nostri occhi e cercheremo di comprendere quali sono i suoi vantaggi. Conclusa la lettura, troverete anche il progetto VB documentato utilizzato per gli esempi di questo articolo. Cominciamo!

Molti libri danno una propria definizione per che cosa sia una classe, ma mi astengo per ora da tale pratica. Sarebbe utile capire da dove si è partiti e qual è l'attuale traguardo. Agli albori dello sviluppo, quando lo spaghetti code spopolava ovunque, per fare i calcoli si utilizzavano i registri del processore, i programmatori assembler sanno bene quanto sia noioso fare il debug con così poca astrazione. In seguito, nei linguaggi più evoluti, sono stati introdotti i tipi base : l'intero, il numerico in virgola mobile a singola e doppia precisione e così via. Era anche necessario accorpare i tipi base in modo da esprimere entità omogenee di elementi e questa astrazione fu raggiunta con i vettori (array). In questo scenario evolutivo fecero anche la loro comparsa le entità eterogenee come le strutture, che tutti noi conosciamo come tipi definiti dall'utente. Fino a questo punto nulla di strano direte voi, ma c'è da notare che negli anni si è sempre ben distinto il ruolo dei dati da quello delle funzioni che operavano su di essi.

La programmazione tradizionale utilizza una tecnica di sviluppo funzionale o procedurale, che fa una netta distinzione dei dati rispetto alle funzioni. Un intero programma è strutturato in moduli che accedono ad uno o più gruppi di dati. Lo sviluppo funzionale ha, però, diversi svantaggi:

- Lo stretto legame che sussiste tra le funzioni ed i dati porta a situazioni in cui singole modifiche si ripercuotono su diverse parti del programma, aumentando la difficoltà di debug ed evidenziando i limiti di tale approccio per uno sviluppo incrementale del software. Per sviluppo incrementale si intende l'evoluzione di un sistema (programma) grazie a modifiche successive che correggono i difetti delle prime versioni ed aumentano le funzionalità delle versioni aggiornate.

- Difficoltà di riutilizzo del software. Ogni volta che si vuole riciclare una funzione è necessario apportare delle modifiche strutturali per adattarla alla nuova applicazione.

Lo sviluppo procedurale è prevalso per molti anni, finché alcune brillanti menti non fecero l'azzardo di accorpare i dati e le funzioni in un'unica entità. Questa entità prende il nome di Classe ed apre nuovi orizzonti per lo sviluppo e la progettazione del software.

Ogni giorno utilizziamo oggetti più o meno complessi senza renderci conto della loro tecnologia interna, quello che ci interessa è la parte esterna che percepiamo. Ad esempio una sveglia ha una propria tecnologia interna, che in genere non ci interessa sapere perché utilizziamo l'interfaccia esterna, insieme di pulsanti con varie funzionalità.

Una classe definisce la tecnologia interna degli oggetti e l'interfaccia che questi avranno verso l'esterno. Quando alla mattina, sotto le calde coperte, ci sentiamo chiamare dalla sveglia, l'istinto di sopravvivenza porta alla ricerca del punto magico in cui la sveglia ha l'interruttore di spegnimento, cioè quella parte della sua interfaccia che la zittisce. Così, come accade per gli oggetti reali anche per gli oggetti nella programmazione si ha una diretta interazione solo con le interfacce pubbliche, che fin ora sono state nominate come esterne.

Le classi permettono di formalizzare la struttura degli oggetti che si desidera rappresentare all'interno di un programma/sistema, da qui il nome di programmazione orientata agli oggetti.

Una classe si compone essenzialmente di due parti:

- dati
- funzioni

I dati di una classe sono chiamati attributi o membri, mentre le funzioni prendono il nome di metodi. Ogni metodo agisce esclusivamente sugli attributi della classe di appartenenza. Questo vincolo rappresenta la grande forza della programmazione ad oggetti, la quale costringe il programmatore ad organizzare il software per componenti riciclabili ben distinti (classi). Una possibile dichiarazione degli attributi di una classe è la seguente:
 
Dim prAttributo1 As Integer
Dim prAttributo2 AS String
Dim prAttributo3 As Single
 
La dichiarazione degli attributi interna alla classe prende il nome di incapsulamento dei dati. Esso consiste nella protezione degli attributi dagli accessi non desiderati.
Un modo più efficace di esprimere gli attributi si raggiunge introducendo le proprietà che accedono alle variabili poch'anzi dichiarate. Ad esempio:
 
Property Let Attributo1(vNewValue As Integer)
	prAttributo1 = vNewValue
End Property

Property Get Attributo1()
	Attributo1 = prAttributo1
End Property
 
Come potete notare le proprietà ci permettono di qualificare un attributo come sola lettura Get, sola scrittura Let (Set se si tratta di variabili oggetto) oppure entrambe, lettura e scrittura. E' buona pratica utilizzare le proprietà per esprimere gli attributi, se non si segue tale regola si rischia di realizzare codice che non sfrutta in modo proficuo la programmazione orientata agli oggetti.

I metodi si dichiarano esattamente come le funzioni e le procedure.
 
Sub Metodo1()
	'qui va il codice
End Sub

Function Metodo2() AS Integer
	'qui va il codice
End Function
 
Sia gli attributi sia i metodi hanno un ambito di validità (scope). Per gli attributi così come per i metodi gli ambiti sono tre: pubblico, privato e protetto. Ecco alcuni esempi che riguardano le proprietà.
 
Private prAttributo1 As Integer
Private prAttributo2 As Integer
Private prAttributo3 As Integer

Public Property Let Attributo1(vNewValue As Integer)
	prAttributo1 = vNewValue
End Property

Public Property Get Attributo1()
	Attributo1 = prAttributo1
End Property

Private Property Let Attributo2(vNewValue As Integer)
	prAttributo2 = vNewValue
End Property

Private Property Get Attributo2()
	Attributo2 = prAttributo2
End Property

Friend Property Let Attributo3(vNewValue As Integer)
	prAttributo3 = vNewValue
End Property

Friend Property Get Attributo3()
	Attributo3 = prAttributo3
End Property
 
Notate che le variabili sono state dichiarate Private, è tramite le proprietà che controlliamo l'accesso ai nostri attributi.
Per la dichiarazione dei metodi il codice è il seguente:
Public Sub Metodo1()
End Sub

Private Sub Metodo2()
End Sub

Friend Sub Metodo3()
End Sub

Public Function Metodo4() As Integer
End Function

Private Function Metodo5() As Integer
End Function

Friend Function Metodo6() As Integer
End Function
 
Spendiamo due parole sull'ambito di validità.
 
Ambito privato
Le dichiarazioni private, come lascia intuire il nome, hanno visibilità esclusivamente all'interno del modulo di classe. Se dichiariamo un membro privato, questo non potrà essere visto all'esterno della classe e di conseguenza nessuno vi potrà accedere. Con un semplice esempio possiamo dire che il contenuto della sveglia, cioè tutti i circuiti elettronici, per noi risulta essere privato, visto che non possiamo interagire direttamente con i vari dispositivi interni.

Ambito pubblico
Le dichiarazioni pubbliche rappresentano di fatto l'interfaccia che i nostri oggetti avranno verso l'esterno, una volta che avremo completato la costruzione della classe. Per fare un confronto con la sveglia, possiamo dire che i suoi pulsanti rappresentano l'interfaccia pubblica.

Ambito protetto
E' molto simile alla visibilità pubblica, ma con una sostanziale differenza: i membri protetti (friend) risultano visibili come se fossero pubblici solo all'interno dello stesso progetto. Questo tipo di scope è indispensabile per realizzare un colloquio protetto tra le classi di uno stesso componente, ma in questo articolo non ne faremo ulteriore menzione.

Dopo questo chiarimento potete intuire che dichiarare una proprietà Private, come è il caso di Attributo2, non ha molto senso visto che all'interno della classe possiamo accedere direttamente alla relativa variabile prAttributo2. Sebbene sia possibile all'interno di una classe dichiarare le variabili con lo scope Public è fortemente sconsigliata questa pratica. La ragione sta nel fatto che la tecnologia interna non deve essere mai direttamente esposta all'esterno.

Per i metodi la situazione è leggermente differente: possiamo realizzare dei metodi di comodo, che non ci interessa esporre all'esterno e di conseguenza attribuire a questi lo scope Private. Possiamo ad esempio avere diversi metodi che producono risultati parziali di una complessa elaborazione, ma all'esterno tali parziali non sono percepiti come risultati di valore.

Un caso banale è rappresentato da un metodo che esegue la lettura di un file un byte alla volta e l'oggetto nel suo complesso deve fornire il contenuto del file per intero.

Visto che abbiamo citato lo scope a questo punto è possibile definire le interfacce di una classe. Un'interfaccia di una classe è l'insieme dei suoi metodi e dei suoi attributi. Più precisamente un'interfaccia pubblica è l'insieme dei metodi e degli attributi pubblici, un interfaccia privata è l'insieme dei metodi e degli attributi privati ed infine, l'interfaccia protetta è l'insieme dei metodi e degli attributi protetti. Da qui possiamo citare uno dei principi fondamentali della programmazione OO: mascheramento dell'informazione. Questo principio fa sì che un oggetto sia diviso in due parti ben distinte: una interfaccia pubblica e una rappresentazione privata o protetta.

Tornando agli attributi, questi si utilizzano per memorizzare lo stato di un oggetto, mentre i metodi servono per i compiti di elaborazione. Dal punto di vista della programmazione, gli attributi, come si è già visto, sono variabili dichiarate all'interno della classe alle quali accediamo tramite le proprietà, mentre i metodi sono rappresentati dalle procedure e dalle funzioni.

Le classi, oltre a quanto descritto, contengono anche un paio di utili eventi: Inizialize e Terminate.
I due eventi vengono generati rispettivamente all'atto della creazione dell'oggetto e prima che l'oggetto venga distrutto. Se non avete confidenza con il concetto di evento (sicuramente sì, ma a scanso di equivoci ^_^) dovete immaginare una procedura che viene eseguita in "automatico" al verificarsi di una certa condizione. Ad esempio l'evento click di un pulsante viene generato quando l'utente esegue un click sul pulsante che è appunto la condizione grazie alla quale l'evento viene attivato. Per ora accontentatevi di queste poche righe, più avanti faremo una panoramica più approfondita sull'argomanto.


I concetti teorici si chiariscono spesso vedendo un esempio pratico. Per rappresentare la figura geometrica rettangolo realizziamo l'omonima classe.

Un rettangolo è caratterizzato da due attributi principali: altezza e base.
Per realizzare una classe in VB è sufficiente creare un modulo di classe all'interno del nostro progetto. Un modulo di classe non è molto diverso da un modulo standard e di conseguenza per i novizi non ci dovrebbero essere grosse difficoltà durante l'inserimento del codice. Gli attributi della classe sono le caratteristiche che abbiamo poch'anzi elencato e li rappresentiamo sotto forma di variabili.
 
Private prAltezza As Single
Private prBase As Single
 
Come avrete notato ho utilizzato la visibilità privata per entrambi gli attributi. Questa scelta, come già sottolineato, ci permette di separare nettamente la tecnologia interna degli oggetti dall'interfaccia che questi avranno verso l'esterno. E' buona pratica dichiarare tutti gli attributi con la visibilità privata. Per le variabili si è utilizzato il prefisso pr per sottolineare che sono private. Non seguire questa regola equivale, nel mondo reale, alla realizzazione di una sveglia con dei fili che penzolano all'esterno. Non è un bel vedere ^_^

Per leggere e scrivere i valori degli attributi privati, realizziamo le apposite proprietà pubbliche Let e Get. Il caso in cui un attributo si riferisce ad una variabile oggetto, si deve fare uso di una proprietà Set (Public Property Set MioOggetto(ByVal vNewValue As Oggetto)) al posto della proprietà Let.
 
Public Property Get Altezza() As Single
	Altezza = prAltezza
End Property

Public Property Let Altezza(ByVal vNewValue As Single)
	prAltezza = vNewValue
End Property

Public Property Get Base() As Single
	Base = prBase
End Property

Public Property Let Base(ByVal vNewValue As Single)
	prBase = vNewValue
End Property
 
Quello che abbiamo creato è la classe Rettangolo che permette di impostare e di leggere i propri attributi Altezza e Base grazie all'interfaccia pubblica costituita dalle proprietà pubbliche. Notate che ogni proprietà è in lettura e scrittura (Get e Let). Se volessimo realizzare una proprietà in sola lettura ci basterebbe rimuovere la relativa proprietà Let/Set.

A partire da questa classe possiamo generare una miriade di oggetti che hanno le caratteristiche di un rettangolo.
Per creare gli oggetti di classe Rettangolo la procedura è semplice. Dichiariamo una variabile riferimento, inizializziamo la variabile con un oggetto creato al volo e quando l'oggetto non ci serve più lo buttiamo via. Possiamo scrivere il seguente codice di prova all'interno dell'evento click di un pulsante.
 
Dim RettangoloDiProva As Rettangolo
	Set RettangoloDiProva = New Rettangolo

	RettangoloDiProva.Altezza = 2
	RettangoloDiProva.Base = 3

	MsgBox "Area rettangolo: " & RettangoloDiProva.Base * RettangoloDiProva.Altezza
	
	Set RettangoloDiProva = Nothing
 
Prima particolarità è la presenza dell'istruzione New, che fa un compito semplicissimo: crea in memoria un oggetto di classe specificata (istanza della classe). L'oggetto creato viene assegnato ad un riferimento tramite il quale ci possiamo riferire all'interfaccia pubblica dell'oggetto. La seconda sta nell'uso del valore particolare Nothing. Quando si assegna il valore Nothing ad un riferimento, questo viene annullato e l'oggetto al quale si riferiva viene cestinato da un particolare meccanismo detto garbage collector. In Visual Basic le risorse occupate da un oggetto vengono completamente rilasciate quando non c'è nessun riferimento che punta ad esso. In parole povere: se nessuno si ricorda dov'è l'oggetto, questo viene buttato via in automatico.

I lettori più attenti si saranno accorti che la classe Rettangolo non definisce nessun metodo, in altre parole non esegue elaborazioni sugli attributi. Per supplire a tale mancanza introduciamo il metodo Area che calcola l'area del rettangolo.
 
Public Function Area() as Single
	Area = prBase * prAltezza
End Function
 
Ecco fatto, ora possiamo ottenere l'area di tutti i rettangoli senza dover fare nessun calcolo diretto con gli attributi. Il codice di prova diventa:
 
Dim RettangoloDiProva As Rettangolo
	Set RettangoloDiProva = New Rettangolo

	RettangoloDiProva.Altezza = 2
	RettangoloDiProva.Base = 3

	MsgBox "Area rettangolo: " & RettangoloDiProva.Area
	
	Set RettangoloDiProva = Nothing
 
Visto com'è facile? Volendo possiamo calcolare la lunghezza delle diagonali interne al rettangolo con quest'altro metodo:
 
Public Function Diagonale() As Single
	Diagonale = Sqr(prAltezza^2 + prBase^2)
End Function
 
Aggiungiamo anche una funzione di test che ci ritorna True se il rettangolo è un quadrato, False altrimenti. Ed un'altra che scali l'oggetto in funzione di un fattore di scala specificato.
 
Public Function Quadrato() As Boolean
	Quadrato = (prAltezza = prBase)
End Function

Public Sub CambiaScala(FattoreScala As Single)
	prAltezza = prAltezza * FattoreScala
	prBase = prBase * FattoreScala
End Sub
 
Come avrete capito non c'è limite alle funzionalità che possiamo aggiungere. Tenete a mente che è buona abitudine utilizzare le procedure e le funzioni per eseguire le elaborazioni sugli attributi di un oggetto, mentre le proprietà dovrebbero servire esclusivamente per validare le assegnazioni e per leggere/scrivere i valori degli attributi.

Riprendiamo con gli eventi standard della classe che abbiamo lasciato da parte. La causa scatenante dell'evento Initialize è la creazione dell'oggetto.
 
'nulla è stato ancora creato
Dim RettangoloDiProva As Rettangolo
	'l'oggetto viene creato e si verifica l'evento Initialize
	Set RettangoloDiProva = New Rettangolo
 
Dualmente, l'evento Terminate si verifica quando l'oggetto viene distrutto.
 
'richiesta di rilascio delle risorse che attiva l'evento Terminate
Set RettangoloDiProva = Nothing
 
In relatà in questo caso la richiesta di rilascio delle risorse è esplicita. Un oggetto viene distrutto anche quando il riferimento ad esso esce dall'ambito di validità (scope) e non vi sono altri riferimenti che puntano ad esso.
 
Public Sub MySub()
' nulla è stato ancora creato
Dim RettangoloDiProva as Rettangolo
	' l'oggetto viene creato e si verifica l'evento Initialize
	Set RettangoloDiProva = New Rettangolo
End Sub
' RettangoloDiProva al di fuori di MySub non ha ragione di esistere ed in
automatico VB cerca di rilasciare le risorse da esso allocate
 
Lo scopo di questi eventi è relativamente semplice. Initialize in genere si utilizza per inizializzare le variabili interne all'oggetto, in modo che questo sia "pronto all'uso". Terminate è comoda per rilasciare esplicitamente eventuali risorse allocate. Ad esempio se usiamo una matrice dinamica di stringhe all'interno della classe è opportuno che prima di terminare la classe lo spazio allocato venga liberato (Erase MyStrArray() ).
Ovviamente nulla vieta di crearsi due appositi metodi InitVars e TearDown e gestire manualmente l'inizializzazione ed il rilascio delle risorse. In alcuni casi gli automatismi VB non bastano e quindi ci si deve arrangiare. Ad esempio, possiamo chiamare i nostri metodi dopo aver creato l'oggetto (InitVars) e prima di inizializzare il riferimento a Nothing (TearDown).
 
'nulla è stato ancora creato
Dim RettangoloDiProva as Rettangolo
l'oggetto viene creato e si verifica l'evento Initialize Set RettangoloDiProva = New Rettangolo 'è un metodo implementato appositamente per inizializzare le variabili RettangoloDiProva.InitVars 'è un metodo implementato appositamente per liberare le risorse RettangoloDiProva.TearDown 'attivazione evento Terminate Set RettangoloDiProva = Nothing
 
Anche se implementiamo i nostri metodi ad hoc, l'esecuzione degli eventi Initialize e Terminate rimane.

Visto che la nostra classe Rettangolo non ha particolari esigenze, possiamo utilizzare l'evento Initialize per impostare a zero sia l'altezza sia la base. Tralasciamo l'evento Terminate perché nel caso in analisi non ha particolare utilità.
 
Private Sub Class_Initialize()
	'inizializzazione membri privati
    prAltezza = 1
    prBase = 1
End Sub
 
Visto che siamo alla fine facciamo un breve riassunto di quanto detto. Le classi racchiudono la definizione degli attributi e dei metodi, i quali hanno un ambito di validità (scope). Le variabili relative agli attributi si dichiarano sempre private e si utilizzano le proprietà per accedervi. I metodi si utilizzano solo per eseguire elaborazioni e possono essere sia funzioni che procedure. Le classi definiscono le caratteristiche degli oggetti e di conseguenza gli oggetti rispecchiano l'implementazione delle classi.
Per la creazione degli oggetti di una determinata classe si utilizza l'istruzione New. Per utilizzare gli oggetti è necessario un riferimento al quale assegnare l'oggetto creato. Per rilasciare le risorse occupate da un oggetto si utilizza il valore Nothing, stando ben attenti a tutti i riferimenti che puntano allo stesso oggetto. Per inizializzare le variabili interne possiamo utilizzare l'evento Initialize e per rilasciare le eventuali risorse allocate l'evento Terminate.

Qui troverete il progetto di esempio con la classe che abbiamo realizzato insieme.

Direi che per essere il primo approccio vi potete accontentare, sono sicuro che non avrete le idee del tutto chiare su come sfruttare questo tipo di programmazione, ma vi assicuro che con un po' di impegno vi sarà facile creare programmi fichissimi scritti in logica object oriented e non vi servirà solo in Visual Basic. Per esercitarvi potete provare a realizzare delle diverse classi a partire dai componenti di una bicicletta. Nel prossimo appuntamento continueremo la nostra esplorazione, nel frattempo esercitatevi finché non sarete in grado di pensare agli oggetti reali in forma di classi con gli attributi ed i metodi ^_^.