|
Eccoci arrivati a parlare degli aspetti peculiari di un linguaggio ad oggetti! Iniziamo con l'Edereditarietà.
L'ereditarietà è la possibilità di un linguaggio ad oggetti di far derivare una classe da un'altra: in questo caso,
la prima assume il nome di classe derivata, mentre la seconda quello di classe base. La classe derivata
acquisisce tutti i membri della classe base, ma può ridefinirli o aggiungerne di nuovi. Questa caratteristica di ogni
linguaggio Object Oriented è particolarmente efficace nello schematizzare una relazione "is-a" (ossia "è un"). Per esempio, potremmo
definire una classe Vegetale, quindi una nuova classe Fiore, che eredita Vegetale. Fiore è un Vegetale, come mostra la struttura gerarchica
dell'ereditarietà. Se definissimo un'altra classe Primula, derivata da Fiore, diremmo che Primula è un Fiore, che a sua volta è un
Vegetale. Quest'ultimo tipo di relazione, che crea classi derivate che saranno basi per ereditare altre classi, si chiama ereditarietà
indiretta.
Passiamo ora a vedere come si dichiara una classe derivata:
Class [Nome]
Inherits [Classe base]
'Membri della classe
End Class
La keyword Inherits specifica quale classe base ereditare: si può avere solo UNA direttiva Inherits per classe, ossia non è possibile
ereditare più classi base. In questo frangente, si può scoprire come le proprietà siano utili e flessibili: se una classe base definisce
una variabile pubblica, questa diverrà parte anche della classe derivata e su tale variabile verranno basate tutte le operazioni che la
coinvolgono. Siccome è possibile che la classe derivata voglia ridefinire tali operazioni e molto probabilmente anche l'utilizzo della
variabile, è sempre consigliabile dichiarare campi Private avvolti da una proprietà, poichè non c'è mai alcun pericolo nel modificare
una proprietà in classi derivate, ma non è possibile modificare i campi nella stessa classe. Un semplice esempio di ereditarietà:
Class Person
'Per velocizzare la scrittura del codice, assumiamo che
'questi campi pubblici siano proprietà
Public FirstName, LastName As String
Public ReadOnly Property CompleteName() As String
Get
Return FirstName & " " & LastName
End Get
End Property
End Class
'Lo studente, ovviamente, è una persona
Class Student
'Student eredita da Person
Inherits Person
'In più, definisce anche questi campi pubblici
'La scuola frequentata
Public School As String
'E l'anno di corso
Public Grade As Byte
End Class
In seguito, si può utilizzare la classe derivata come si è sempre fatto con ogni altra classe. Nel farne uso, tuttavia,
è necessario considerare che una classe derivata possiede non solo i membri che il programmatore ha esplicitamente definito nel suo
corpo, ma anche tutti quei membri presenti nella classe base che si sono implicitamente acquisiti nell'atto stesso di scrivere "Inherits".
Se vogliamo, possiamo assimilare una classe ad un insieme, i cui elementi sono i suoi membri: una classe base è sottoinsieme della
corrispondente classe derivata. Di solito, l'ambiente di sviluppo aiuta molto in questo, poiché, nei suggerimenti proposti durante
la scrittura del codice, vengono automaticamente inserite anche le voci ereditate da altre classi. Ciò che abbiamo appena visto
vale anche per ereditarietà indiretta: se A eredita da B e B eredita da C, A disporrà dei membri di B, alcuni dei quali sono
anche membri di C (semplice proprietà transitiva).
Ora, però, bisogna porre un bel Nota Bene alla questione. Infatti, non tutto è semplice come sembra. Forse nessuno si è
chiesto che fine fanno gli specificatori di accesso quando un membro viene ereditato da una classe derivata. Ebbene, esistono delle precise
regole che indicano come gli scope vengono trattati quando si eredita:
- Un membro Public o Friend della classe base diventa un membro Public o Friend della classe derivata (in pratica, non cambia
nulla; viene ereditato esattamente com'è);
- Un membro Private della classe base non è accessibile dalla classe derivata, poichè il suo ambito di visibilità impedisce a ogni
chiamante esterno alla classe base di farvi riferimento, come già visto nelle lezioni precedenti;
- Un membro Protected della classe base diventa un membro Protected della classe derivata, ma si comporta come un membro Private.
Ed ecco che abbiamo introdotto uno degli specificatori che ci eravamo lasciati indietro. I membri Protected sono particolarmente utili
e costituiscono una sorta di "scappatoia" al fatto che quelli privati non subiscono l'ereditarietà. Infatti, un memebro Protected si
comporta esattamente come uno Private, con un'unica eccezione: è ereditabile, ed in questo caso diventa un membro Protected della
classe derivata. Lo stesso discorso vale anche per Protected Friend. Ecco uno schema che esemplifica il comportamento dei principali Scope:

Esempio:
Module Esempio
Class Person
'Due campi protected
Protected _FirstName, _LastName As String
'Un campo private readonly: non c'è ragione di rendere
'questo campo Protected poichè la data di nascita non
'cambia ed è sempre accessibile tramite la proprietà
'pubblica BirthDay
Private ReadOnly _BirthDay As Date
Public Property FirstName() As String
Get
Return _FirstName
End Get
Set(ByVal Value As String)
If Value <> "" Then
_FirstName = Value
End If
End Set
End Property
Public Property LastName() As String
Get
Return _LastName
End Get
Set(ByVal Value As String)
If Value <> "" Then
_LastName = Value
End If
End Set
End Property
Public ReadOnly Property BirthDay() As Date
Get
Return _BirthDay
End Get
End Property
Public ReadOnly Property CompleteName() As String
Get
Return _FirstName & " " & _LastName
End Get
End Property
'Costruttore che accetta tra parametri obbligatori
Sub New(ByVal FirstName As String, ByVal LastName As String, _
ByVal BirthDay As Date)
Me.FirstName = FirstName
Me.LastName = LastName
Me._BirthDay = BirthDay
End Sub
End Class
'Lo studente, ovviamente, è una persona
Class Student
'Student eredita da Person
Inherits Person
'La scuola frequentata
Private _School As String
'E l'anno di corso
Private _Grade As Byte
Public Property School() As String
Get
Return _School
End Get
Set(ByVal Value As String)
If Value <> "" Then
_School = Value
End If
End Set
End Property
Public Property Grade() As Byte
Get
Return _Grade
End Get
Set(ByVal Value As Byte)
If Value > 0 Then
_Grade = Value
End If
End Set
End Property
'Questa nuova proprietà si serve anche dei campi FirstName
'e LastName nel modo corretto, poichè sono Protected anche
'nella classe derivata e fornisce un profilo completo
'dello studente
Public ReadOnly Property Profile() As String
Get
'Da notare l'accesso a BirthDay tramite la proprietà
'Public: non è possibile accedere al campo _BirthDay
'perchè è privato nella classe base
Return _FirstName & " " & _LastName & ", nato il " & _
BirthDay.ToShortDateString & " frequenta l'anno " & _
_Grade & " alla scuola " & _School
End Get
End Property
'Altra clausola importante: il costruttore della classe
'derivata deve sempre richiamare il costruttore della
'classe base
Sub New(ByVal FirstName As String, ByVal LastName As String, _
ByVal BirthDay As Date, ByVal School As String, _
ByVal Grade As Byte)
MyBase.New(FirstName, LastName, BirthDay)
Me.School = School
Me.Grade = Grade
End Sub
End Class
Sub Main()
Dim P As New Person("Pinco", "Pallino", Date.Parse("06/07/90"))
Dim S As New Student("Tizio", "Caio", Date.Parse("23/05/92"), _
"Liceo Classico Ugo Foscolo", 2)
Console.WriteLine(P.CompleteName)
'Come si vede, la classe derivata gode degli stessi membri
'di quella base, acquisiti secondo le regole
'dell'ereditarietà appena spiegate
Console.WriteLine(S.CompleteName)
'E in più ha anche i suoi nuovi membri
Console.WriteLine(S.Profile)
'Altra cosa interessante: dato che Student è derivata da
'Person ed espone tutti i membri di Person, più altri,
'non è sbagliato assegnare un oggetto Student a una
'variabile Person
P = S
Console.WriteLine(P.CompleteName)
Console.ReadKey()
End Sub
End Module
L'output:
Pinco Pallino
Tizio Caio
Tizio Caio, nato il 23/5/1992 frequenta l'anno 2 alla scuola Liceo Classico Ugo
Foscolo
Tizio Caio
(Per maggiori informazioni sulle operazioni con le date, vedere il capitolo 57)
Anche se il sorgente è ampiamente commentato mi soffermerei su alcuni punti caldi. Il costruttore della classe derivata deve sempre
richiamare il costruttore della classe base, e questo avviene tramite la keyword MyBase che, usata in una classe derivata, fa riferimento alla
classe base corrente: attraverso questa parola riservata è possibile anche raggiungere i membri privati della classe base, ma si fa
raramente, poichè il suo impiego più frequente è quello di riprendere le vecchie versioni di metodi modificati. Il secondo punto riguarda
la conversione di classi: passare da Student a Person non è, come potrebbe sembrare, una conversione di riduzione, poichè durante il
processo, nulla va perduto nel vero senso della parola. Certo, si perdono le informazioni supplementari, ma alla classe base queste non servono:
la sicurezza di eseguire la conversione risiede nel fatto che la classe derivata gode degli stessi membri di quella base e quindi non si
corre il rischio che ci sia riferimento a un membro inesistente. Questo invece si verifica nel caso opposto: se una variabile di tipo Student
assumesse il valore di un oggetto Person, School e Grade sarebbero privi di valore e ciò generebbe un errore. Per eseguire questo tipo di
passaggi è necessario l'operatore DirectCast.
|
|