Programmazione ad oggetti - II
|
Domande
|
Classi
In VB.NET definiamo una classe usando le parole chiave: Class... End Class.
Esempio: definiamo la classe 'Batteria' con la quale vogliamo rappresentare una
pila del mondo reale.
Class Batteria
Private m_Voltaggio As Double
Private m_Livello As Integer
Public Sub New()
m_Voltaggio = 0
m_Livello = 0
End Sub
Public Function Alimenta(ByVal Corrente As Double) As Boolean
If m_Livello = 0 Then
return False
Else
m_Livello = m_Livello - 1
return True
End If
End Function
End Class
Col colore verde indichiamo la dichiarazione delle variabili che caratterizzano
la struttura interna di ogni oggetto che creato con questa classe. Tali
variabili vengono dette campi o membri e convenzionalmente diamo
loro un nome che comincia con 'm_'. In blu abbiamo scritto il costruttore. In
VB.NET il costruttore e' una 'Sub' (procedura) e si dichiara con il nome
New. Ogni volta che incontriamo una sub New all'interno di una
classe, sappiamo che quello e' un costruttore. Come vederemo, possono esserci
piu' costruttori. In rosso indichiamo i metodi.
Nella classe Batteria, m_Livello memorizza il livello di carica della batteria,
analogamente m_Voltaggio indica la tensione nominale della pila (ad es. 1.5
Volts, 5 Volts, etc). Ora, non ci proponiamo di fare una simulazione fisica dei
processi che intervengono all'interno di una batteria reale, quindi i calcoli e
le relazioni fra le grandezze in gioco saranno essenzialmente 'pro forma'.
Quando istanziamo la batteria, osserviamo che il costruttore la crea 'scarica'
(tutti e due i campi sono uguali a zero). Il metodo Alimenta e' una funzione
che permette di far consumare alla batteria una certa quantita' di energia, in
base alla corrente richiesta nel parametro. Qui ignoriamo il parametro e ci
limitaimo a fornire sempre la stessa quantita' di energia, cioe' decrementiamo
il livello di carica sempre di uno. Alimenta restituira' 'Falso' se non e' in
grado piu' in grado di erogare energia (pila scarica). Se restituisce 'Vero',
allora l'utilizzatore dell'oggetto sara' autorizzato a compiere il proprio
compito (accendere un oggetto lampadina ad esempio), altrimenti smettera' di
funzionare.
Istanze ed Handle
Quando vogliamo utilizzare una batteria dobbiamo innanzitutto creare l'oggetto.
Lo facciamo nello stesso modo in cui dichiariamo una variabile, ma ci sono
diverse osservazioni da fare:
Dim hBatteria As Batteria
hBatteria = New Batteria()
oppure
Dim hBatteria As Batteria = New Batteria()
oppure
Dim hBatteria As New Batteria()
In realta' quando istanziamo un oggetto, la variabile che utilizziamo non e'
l'oggetto stesso, ma un riferimento ad esso. Questa variabile speciale e' detta handle.
La possiamo considerare come una chiave d'accesso all'oggetto. Nel primo caso
dichiariamo il riferimento all'oggetto (notate l'assenza delle parentesi tonde
dopo il nome della classe) e solo successivamente istanziamo l'oggetto, con la
parola chiave New. Trovando la parola chiave New ci viene in mente che
stiamo invocando proprio il costruttore. Ricapitolando, quando abbiamo un
riferimento ad un oggetto, non necessariamente abbiamo anche l'oggetto. Per
ottenere l'oggetto dobbiamo usare New. La seconda forma di istanziamento e' una
forma contratta che raccoglie i due passi del caso precedente in un' unica
istruzione, dichiarazione piu' inizializzazione. In questo modo nasce
contemporaneamente l'handle e l'oggetto a cui l'handle si riferisce. La terza forma e'
simile alla seconda.
Proprieta'
Il concetto di proprieta' e' un'estensione alla programmazione ad oggetti
introdotto dal Visual Basic. Le proprieta' sono costrutti simili a procedure e
funzioni. Essi danno volutamente l'impressione che stiamo accedendo
direttamente ad un campo interno dell'oggetto, ma in realta' non e' cosi'.
Abbiamo definito la classe Batteria in modo che, al momento della costruzione,
l'oggetto pila sia scarico. Dobbiamo pur trovare un modo di caricarlo. Potremmo
usare un metodo che chiameremo Carica, al quale passiamo un valore che
definisce il livello di carica, oppure possiamo creare una proprieta' alla
quale assegnare direttamente il valore della carica. Nel primo caso,
scriveremo:
hBatteria.Carica(100)
nel secondo caso:
hBatteria.Livello = 100
dove 'Livello' e' il nome della proprieta'.
Se avessimo dato al campo m_Livello l'attributo Public, avremmo potuto
modificare direttamente il suo valore scrivendo: 'hBatteria.m_Livello = ...".
Abbiamo pero' visto che, per ragioni di incaspulamento, la struttura interna
deve rimanere nascosta ed allora questa tecnica va utilizzata raramente. Quindi
per ottenere qualcosa di simile, utilizziamo le proprieta'. Notate che non
esiste nessuna variabile membro chiamata Livello.
Implementiamo sia Carica che Livello:
Class Batteria
' ...
Public Sub Carica(ByVal Livello As Integer)
If Livello > 100 Then Livello = 100
m_Livello = Livello
End Sub
Public Property Livello() As Integer
Get
return m_Livello
End Get
Set
If Livello > 100 Then Livello = 100
m_Livello = Value
End Set
End Property
End Class
Il metodo Carica, in rosso, si limita a copiare il valore di Livello nel
campo m_Livello. Abbiamo evidenziato la proprieta' in verde. Il suo corpo e'
racchiuso tra Property... End Property. Nell'intestazione la parola As
ci permette di specificare il tipo di dato che la proprieta' rappresenta; in
questo caso Livello e' un intero, coerentemente con 'm_Livello'. Notate che
al suo interno ci sono due sezioni. La sezione Set indica il gruppo di
operazioni che verranno compiute quando alla proprieta' si assegna un valore.
Se guardate bene, noterete che questa parte e' praticamente il corpo della
procedura Carica. Osservate che il valore assegnato alla proprieta' e'
identificato dalla parola chiave Value. La sezione costituita dal Get
invece, viene eseguita quando si tenta di utilizzare il valore contenuto nella
proprieta' (ad esempio x = hBatteria.Livello). In conclusione, Get indica
quali passi eseguire per passare il valore della proprieta' al codice
chiamante, Set indica come e dove memorizzare il valore che si assegna.
Osservate che non deve esistere necessariamente un campo che faccia da appoggio
al valore della proprieta'. Molte volte in Get, ad esempio, e' comune vedere il
valore sintetizzato tramite formule che utilizzano altri campi: 'return m_Base
* m_Altezza / 2'.
Implementiamo anche la proprieta' Voltaggio:
Class Batteria
' ...
Public Property Voltaggio() As Double
Get
return m_Voltaggio
End Get
Set
m_Voltaggio = Value
End Set
End Property
End Class
Eventi
Oltre a Proprieta' e Metodi, con VB.NET una classe puo' definire Eventi. In
genere una programmazione ad oggetti classica, invece, simula gli eventi
tramite interfacce ed ereditarieta' che vedremo in seguito. Un evento non e'
altro un qualcosa che accade e tramite esso l'oggetto informa il mondo esterno.
Un bambino che piange e' un evento che puo' informare i genitori che ha fame.
La nostra batteria potrebbe informare l'uilizzatore che la carica e' esaurita.
Chiamiamo questo evento Scarica. E' compito dell'oggetto generare l'evento
quando e' il momento. Riscriviamo parzialmente la classe evidenziando la
gestione dell'evento:
Class Batteria
Public Event Scarica()
' ...
Public Function Alimenta(ByVal Corrente As Double) As Boolean
If m_Livello = 0 Then
Return False
Else
m_Livello = m_Livello - 1
If m_Livello = 0 Then RaiseEvent Scarica()
Return True
End If
End Function
' ...
End Class
In rosso sono indicate le modifiche apportate alla classe. Per poter generare
un evento bisogna specificare il suo prototipo con la parola chiave Event.
Notate che il prototipo e' simile a quello di una procedura. Tra le parentesi
tonde avremmo potuto inserire una lista di paramtri se fosse stato il caso.
Quando abbiamo bisogno di generare l'evento lo lanciamo con la parola chiave RaiseEvent.
In RaiseEvent specifichiamo il nome dell'evento e l'eventuale lista di
parametri come se stessimo invocando una funzione.
Cosa ne sara' dell'evento? Esso puo' essere intercettato dalla sezione di
codice che utilizza l'oggetto. Ad esempio:
Dim WithEvents hBatteria As Batteria
hBatteria = New Batteria()
Public Sub Batteria_Scarica() Handles hBatteria.Scarica
MessageBox.Show("La batteria e' scarica")
End Sub
Per abilitare la ricezione degli eventi generati da un oggetto possiamo
dichiarare l'oggetto aggiungendo WithEvents alla sua dichiarazione.
L'evento puo' essere agganciato ad una
procedura. Nell'esempio abbiamo associato la Sub Batteria_Scarica all'evento
hBatteria.Scarica tramite la parole chiave Handles.
E' facile capire cosa
succedera' quando l'evento verra' generato: comparira' una MessageBox che
avvisera' l'utente.
Segue la classe completa e l'esempio di utilizzo in un Form di Windows.
Public Class Batteria
Public Event Scarica()
Private m_Voltaggio As Double
Private m_Livello As Integer
Public Sub New()
m_Voltaggio = 0
m_Livello = 0
End Sub
Public Function Alimenta(ByVal Corrente As Double) As Boolean
If m_Livello = 0 Then
Return False
Else
m_Livello = m_Livello - 1
If m_Livello = 0 Then RaiseEvent Scarica()
Return True
End If
End Function
Public Sub Carica(ByVal Livello As Integer)
If Livello > 100 Then Livello = 100
m_Livello = Livello
End Sub
Public Property Livello() As Integer
Get
Return m_Livello
End Get
Set(ByVal Value As Integer)
If Livello > 100 Then Livello = 100
m_Livello = Value
End Set
End Property
Public Property Voltaggio() As Double
Get
Return m_Voltaggio
End Get
Set(ByVal Value As Double)
m_Voltaggio = Value
End Set
End Property
End Class
' Sezione di codice estratta dal form
Dim WithEvents hBatteria As Batteria
Public Sub Batteria_Scarica() Handles hBatteria.Scarica
MessageBox.Show("La batteria e' scarica")
End Sub
Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
hBatteria = New Batteria()
hBatteria.Voltaggio = 5
hBatteria.Livello = 3
TextBox1.Text = hBatteria.Livello
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
hBatteria.Alimenta(1)
TextBox1.Text = hBatteria.Livello
End Sub
Ereditarieta'
Si dice spesso che la programmazione ad oggetti trae vantaggio dal riutilizzo
del codice. Questo non vuol dire copiare nei propri sorgenti una classe scritta
da qualcun altro. L'ereditarieta' e' quella caratteristica che permette di
riutilizzare il codice. Si ha ereditarieta' quando una nuova classe, detta
derivata, e' costruita sulla struttura di una classe gia' esistente, detta
classe base. La classe derivata quindi, e' una classe che ha gia' una
funzionalita' di base che lo sviluppatore non deve riscrivere. Notate che qui
non e' importante avere a disposizione il codice della classe base per
derivare. A partire del comportamento base dell'oggetto, lo sviluppatore puo'
ampliare la sua struttura (dati piu' metodi) specializzandone le funzionalita'.
Pensate al rapporto che intercorre tra l'oggetto Rettangolo e l'oggetto Quadro.
Fisicamente e' facile pensare il Quadro derivato dal Rettangolo, infatti il
Quadro e' anche un Rettangolo. La derivazione lo porta poi anche all'abilita'
di disegnare un'immagine al suo interno. Questo esempio dimostra visivamente
come il Quadro abbia ereditato la struttura del Rettangolo, cioe' la sua forma.
Su quella stessa forma si e' poi costruito il resto. Descriviamo le classi che
definiscono i due oggetti.
Public Class Rettangolo
Protected m_X As Integer
Protected m_Y As Integer
Protected m_Base As Integer
Protected m_Altezza As Integer
Public Sub New(ByVal x As Integer, ByVal y As Integer, _
ByVal base As Integer, ByVal altezza As Integer)
m_X = x
m_Y = y
m_Base = base
m_Altezza = altezza
End Sub
' metodi ...
End Class
Public Class Quadro
Inherits Rettangolo
Private m_Immagine As Integer
Public Sub New(ByVal x As Integer, ByVal y As Integer, _
ByVal base As Integer, ByVal altezza As Integer)
MyBase.New(x, y, base, altezza)
m_Immagine = 0
End Sub
Public Sub InserisciImmagine(ByVal i As Integer)
m_Immagine = i
End Sub
Public Sub Disegnati()
' disegna l'immagine numero i
End Sub
End Class
Volendoci dedicare all'ereditarieta', diciamo subito che tralasceremo i
dettagli tecnici della gestione dell'immagine; per non appesantire, supponiamo
di identificare l'immagine con un indice (numero intero).
La prima cosa che abbiamo evidenziato e' la parola chiave che ci permette di
realizzare l'ereditarieta' Inherits. Dal momento che stiamo ereditando,
ci basta definire soltanto il campo m_Immagine, che conterra' l'indice
dell'immagine da visualizzare. Avendo ereditato, alla classe Quadro
appartengono anche i campi Protected della classe Rettangolo. Se avessimo
definito i campi Private ne avremmo impedito l'accesso da parte della classe
derivata. La parola chiave MyBase e' un riferimento alla classe base.
Notate che tramite MyBase noi invochiamo direttamente il costruttore della
classe base. Cio' e' necessario perche' avvenga la sua corretta
inizializzazione. Infatti, come potrebbe costruirsi la classe Rettangolo senza
sapere i suoi parametri? Glieli forniamo noi nel costruttore derivato. Notate
invochiamo il costruttore base per primo.
A questo punto, in Quadro, abbiamo a disposizione tutti i campi ed i metodi
della classe base che non siano dichiarati come 'Private'. A questi ne abbiamo
aggiunti altri che specializzano l'impiego dell'oggetto.
Overriding
L'overriding si riferisce ai metodi che una classe eredita. La classe derivata
puo' ridefinire metodi della classe base. Quando si ridefinisce (overriding)
un metodo, il nuovo metodo deve avere lo stesso nome e la stessa lista di
parametri del vecchio metodo, quello che cambia e' l'implementazione:
' Metodo della classe base
Public Overridable Sub Messaggio(ByVal msg As String)
MessageBox.Show(msg)
End Sub
' Metodo della classe derivata
Public Overrides Sub Messagio(ByVal msg As String)
MessageBox.Show("(" & msg & ")")
End Sub
Nell'esempio, la classe derivata ora dispone di un metodo 'Messaggio' che e'
stato sovrascritto. Quando invocheremo tale metodo, l'oggetto visualizzera' lo
stesso messaggio che avrebbe visualizzato se non avesse reimplementato il
metodo, ed in piu' racchiude il messaggio tra le parentesi tonde. Per abilitare
l'overriding di un metodo bisogna associargli l'attributo Overridable e
nel sovrascriverlo si usa l'attributo Overrides.
Overloading
Anche l'overloading si riferisce ai metodi che una classe eredita. Nella classe
derivata un metodo puo' essere definito piu' volte usando lo stesso nome, ma
variando la lista di parametri:
' Metodo della classe base
Public Sub Incrementa(ByVal step As Integer)
valore = valore + step
End Sub
' Metodi della classe derivata
Public Overloads Sub Incrementa(ByVal step As Double)
valore = valore + step
End Sub
Public Overloads Sub Incrementa(ByVal step_l As Integer, _
ByVal step_h As Integer)
valore = valore + (step_h * 256 + step_l)
End Sub
L'attributo Overloads permette di 'sovraccaricare' un metodo con diverse
implementazioni. Quando invochiamo tale metodo individuiamo una particolare
implementazione in base al tipo e/o numero di parametri che gli forniamo.
Poiche' ad un ben preciso nome di metodo facciamo corrispondere diverse
implementazioni, qualcuno definisce l'overloading come Polimorfismo
Polimorfismo
Il polimorfismo e' una conseguenza dell'ereditarieta'. Ricordiamo che cosa e'
un Handle. Esso e' una chiave di accesso a metodi, proprieta', eventi ed
anche a campi Public dell'oggetto. Poiche' un oggetto della classe derivata
e' anche un oggetto della classe base, noi possiamo riferirci ad esso tramite
un handle della classe base. Ovviamente, in tal modo, potremo accedere
solamente ai metodi della classe base.
Un primo vantaggio di questo fatto e' che, se abbiamo piu' classi che derivano
tutte dalla stessa classe, possiamo trattarle tutte in maniera uniforme grazie
ai metodi in comune. Con essi possiamo gestirle indipendentemete dal loro tipo.
Pensiamo al problema della gestione di un insieme di oggetti. Gli elementi
dell'insieme devono essere tutti dello stesso tipo. Con l'ereditarieta', se
tutti gli oggetti che vogliamo trattare sono derivati dalla stessa classe base,
il tipo comune e' proprio quello della classe base.
Inoltre, l'overriding permette di cambiare l'implementazione del metodo
ereditato. Ora, supponiamo che tutte le classi derivate a cui appartengono gli
oggetti dell'insieme abbiamo uno stesso metodo ereditato sovrascritto ciascuno
in maniera differente. Invocando tale metodo per ogni oggetto dell'insieme
vedermo comportamenti diversi.
' B e C derivano da A
'
' Classe A
' |________Classe B
' |
' |________Classe C
'
Public Class A
' Resto della classe, omesso
' Metodo della classe base, A
Public Sub Descrizione()
MessageBox.Show("Sono la classe A")
End Sub
End Class
Public Class B
' Resto della classe, omesso
' Metodo della classe derivata B derivata da A
Public Overrides Sub Descrizione()
MessageBox.Show("Sono la classe B")
End Sub
End Class
Public Class C
' Resto della classe, omesso
' Metodo della classe derivata C derivata da A
Public Overrides Sub Descrizione()
MessageBox.Show("Sono la classe C")
End Sub
End Class
...
' Test
Dim obj As A
Dim obj_b As New B()
Dim obj_c As New C()
obj = obj_b
obj.Descrizione() ' scrivera': Sono la classe B
obj = obj_c
obj.Descrizione() ' scrivera': Sono la classe C
Nell'esempio, abbiamo definito tre classi, A e' la classe base, B e C derivano
da essa e cambiano l'implementazione del metodo 'Descrizione()' che e'
ereditato. Nel test abbiamo dichiarato tre variabili oggetto e due oggetti, uno
di tipo B e l'altro di tipo C. La variabile obj e' soltanto un riferimento ad
un eventuale oggetto del tipo A. La prima assegnazione associa il riferimento
obj all'oggetto obj_b, cio' e' possibile perche', benche' appartengano a tipi
diversi, obj_b appartiene anche alla classe A. Successivamente
all'assegnazione, invochiamo il metodo descrizione. Poiche' obj e' associato a
obj_b e poiche' nella classe B 'Descrizione()' e' stato sovrascritto, il
risultato dell'invocazione sara' il messaggio: Sono la classe B. Allo stesso
modo quando associamo obj a obj_c otterremo l'altro messaggio: Sono la classe
C. Al comportamento descritto si da' il nome di Polimorfismo perche',
invocazioni di uno stesso metodo su oggetti diversi, ottiene risposte
diverse.
Metodi statici
La differenza tra una classe ed un oggetto e' che un oggetto e' un'istanza
della classe, la classe e' il modello dell'oggetto. Quindi, se vogliamo
invocare un metodo dobbiamo prima creare l'oggetto. Esistono, pero', dei metodi
che possono essere invocati indipendentemente dall'instanziamento. Tali metodi
vengono detti statici o Shared in VB.NET. Insieme ai metodi anche i
campi possono essere definiti statici. Si utilizza l'attributo Shared come
mostra l'esempio. Un membro shared e' unico per tutte le istanze di una classe.
Public Class Test
Public Shared membroCondiviso As Integer
' Altri campi ...
Public Shared Sub Metodo_Shared()
MessageBox.Show("shared")
End Sub
Public Sub Metodo_NonShared()
MessageBox.Show("non shared")
End Sub
' Altri metodi ...
End Class
...
'Verifica
Dim x As Integer
x = Test.membroCondiviso ' ok
Test.Metodo_Shared() ' ok
Test.Metodo_NonShared() ' Errore
dim obj As Test = New Test()
obj.Metodo_NonShared() ' ok
Interfacce
Come la maggior parte dei concetti relativi all'OOP, l'interfaccia rappresenta
un modo di concepire il codice. L'interfaccia e' simile ad una classe, ma non
e' istanziabile. Ogni interfaccia deve essere implementata, in quanto manca del
codice vero e proprio. Implementiamo l'interfaccia con una classe che eredita
da essa. L'interfaccia costituisce un modello, un insieme di metodi senza corpo
che qualunque classe che la voglia implementare, dovra' definire.
Public Interface IStrumentoMusicale
Sub Suona(ByVal Nota As Integer, ByVal Durata As Integer)
Sub Pausa(ByVal Durata As Integer)
End Interface
Public Class Violino
Implements IStrumentoMusicale
Public Sub Suona(ByVal Nota As Integer, ByVal Durata As Integer) _
Implements IStrumentoMusicale.Suona
MessageBox.Show("Violino: suona")
End Sub
Public Sub Pausa(ByVal Durata As Integer) _
Implements IStrumentoMusicale.Pausa
MessageBox.Show("Violino: pausa")
End Sub
End Class
Public Class Pianoforte
Implements IStrumentoMusicale
Public Sub Suona(ByVal Nota As Integer, ByVal Durata As Integer) _
Implements IStrumentoMusicale.Suona
MessageBox.Show("Pianoforte: suona")
End Sub
Public Sub Pausa(ByVal Durata As Integer) _
Implements IStrumentoMusicale.Pausa
MessageBox.Show("Pianoforte: pausa")
End Sub
End Class
Public Class Chitarra
Implements IStrumentoMusicale
Public Sub Suona(ByVal Nota As Integer, ByVal Durata As Integer) _
Implements IStrumentoMusicale.Suona
MessageBox.Show("Chitarra: suona")
End Sub
Public Sub Pausa(ByVal Durata As Integer) _
Implements IStrumentoMusicale.Pausa
MessageBox.Show("Chitarra: pausa")
End Sub
End Class
L'esempio presentato mostra un'interfaccia e tre implementazioni della stessa.
L'interfaccia e' definita dal blocco Interface...
End Interface. Notate che i metodi che essa propone sono prototipi,
essi non hanno corpo, indicano i metodi che dovranno necessariamente essere
implementati se si vuole ereditare dall'interfaccia. Le classi Violino,
Pianoforte, Chitarra, implementano, ciascuna, tutti e due i metodi. Ovviamente
ognuna li implementa in maniera diversa. Una classe indica che sta
implementando un'interfaccia con la parola chiave Implements. La parola
Implements e' usata anche nella definizione del metodo implementatore, con essa
indichiamo quale metodo stiamo implementando.
In VB.NET, le interfacce vengono usate per simulare quello che in altri
linguaggi viene detta ereditarieta' multipla. Una classe puo' infatti
implementare piu' interfacce, ma non puo' ereditare da piu' di una classe.
Le interfacce sono utili, ad esempio, nella gestione degli eventi. Senza
dilungarci, diciamo che potremmo creare un interfaccia EM che contenga i
prototipi degli eventi del mouse, un'altra ET, che contenga quelli della
tastiera , etc. Quando vogliamo essere informati di qualche evento, ci basta
far implementare le interfacce appena dette da un oggetto A ed inserirlo nella
lista di un oggetto B che fornisce gli eventi. Ad evento accaduto, l'oggetto B
richiamera' il metodo relativo, definito in una delle suddette interfacce.
Questo metodo, implementato dal nostro oggetto A, non solo viene
invocato, notificando dell'evento occorso, ma e' in grado di rispondere
adeguatamente con la sua implentazione.
Ultimo aggiornamento 13/01/2004
|