Il polimorfismo è la capacità di un linguaggio ad oggetti di ridefinire i membri della classe base in modo tale che si comportino
in maniera differente all'interno delle classi derivate. Questa possibilità è quindi strettamente legata all'ereditarietà. Le keywords
che permettono di attuarne il funzionamento sono due: Overridable e Overrides. La prima deve marcare il membro della
classe base che si dovrà ridefinire, mentre la seconda contrassegna il membro della classe derivata che ne costituisce la nuova versione.
È da notare che solo membri della stessa categoria con
nome uguale e signature identica (ossia con lo stesso numero e lo stesso tipo di parametri)
possono subire questo processo: ad esempio non si può ridefinire la procedura ShowText() con la proprietà Text, perchè hanno
nome differente e sono di diversa categoria (una è una procedura e l'altra una proprietà). La sintassi è semplice:
Class [Classe base]
Overridable [Membro]
End Class
Class [Classe derivata]
Inherits [Classe base]
Overrides [Membro]
End Class
Questo esempio prende come base la classe Person definita nel capitolo precedente e sviluppa da questa la classe Teacher (insegnante),
modificandone le proprietà LastName e CompleteName:
Module Module1
Class Person
Protected _FirstName, _LastName As String
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
'Questa proprietà sarà ridefinita nella classe Teacher
Public Overridable 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
'Questa proprietà sarà ridefinita nella classe Teacher
Public Overridable 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
Class Teacher
Inherits Person
Private _Subject As String
Public Property Subject() As String
Get
Return _Subject
End Get
Set(ByVal Value As String)
If Value <> "" Then
_Subject = Value
End If
End Set
End Property
'Ridefinisce la proprietà LastName in modo da aggiungere
'anche il titolo di Professore al cognome
Public Overrides Property LastName() As String
Get
Return "Prof. " & _LastName
End Get
Set(ByVal Value As String)
'Da notare l'uso di MyBase e LastName: in questo
'modo si richiama la vecchia versione della
'proprietà LastName e se ne imposta il
'valore. Viene quindi richiamato il blocco Set
'vecchio: si risparmiano due righe di codice
'poichè non si deve eseguire il controllo
'If su Value
MyBase.LastName = Value
End Set
End Property
'Ridefinisce la proprietà CompleteName in modo da
'aggiungere anche la materia insegnata e il titolo di
'Professore
Public Overrides ReadOnly Property CompleteName() As String
Get
'Anche qui viene richiamata la vecchia versione di
'CompleteName, che restituisce semplicemente il
'nome completo
Return "Prof. " & MyBase.CompleteName & _
", dottore in " & Subject
End Get
End Property
Sub New(ByVal FirstName As String, ByVal LastName As String, _
ByVal BirthDay As Date, ByVal Subject As String)
MyBase.New(FirstName, LastName, BirthDay)
Me.Subject = Subject
End Sub
End Class
Sub Main()
Dim T As New Teacher("Mario", "Rossi", Date.Parse("01/01/1950"), _
"Letteratura italiana")
'Usiamo le nuove proprietà, ridefinite nella classe
'derivata
Console.WriteLine(T.LastName)
'> "Prof. Rossi"
Console.WriteLine(T.CompleteName)
'> "Prof. Mario Rossi, dottore in Letteratura italiana"
Console.ReadKey()
End Sub
End Module
In questo modo si è visto come ridefinire le proprietà. Ma prima di proseguire vorrei far notare un comportamento particolare:
Dim P As Person = T
Console.WriteLine(P.LastName)
Console.WriteLine(P.CompleteName)
In questo caso ci si aspetterebbe che le proprietà richiamate da P agiscano come specificato nella classe base (ossia senza includere
altre informazioni se non il nome ed il cognome), poiché P è di quel tipo. Questo, invece, non accade. Infatti, P e T, dato
che abbiamo usato l'operatore =, puntano ora allo stesso oggetto in memoria, solo che P lo vede come di tipo Person e T come di tipo Teacher.
Tuttavia, l'oggetto reale è di tipo Teacher e perciò i suoi metodi sono a tutti gli effetti quelli ridefiniti nella classe
derivata. Quando P tenta di richiamare le proprietà in questione, arriva all'indirizzo di memoria dove sono conservate le istruzioni
da eseguire, solo che queste si trovano all'interno di un oggetto Teacher e il loro codice è, di conseguenza, diverso da quello
della classe base. Questo comportamento, al contrario di quanto potrebbe sembrare, è utilissimo: ci permette, ad esempio, di
memorizzare in un array di persone sia studenti che insegnanti, e ci permette di scrivere a schermo i loro nomi differentemente senza
eseguire una conversione. Ecco un esempio:
Dim Ps(2) As Person
Ps(0) = New Person("Luigi", "Ciferri", Date.Parse("7/7/1982"))
Ps(1) = New Student("Mario", "Bianchi", Date.Parse("19/10/1991"), _
"Liceo Scientifico Tecnologico Cardano", 5)
Ps(2) = New Teacher("Ubaldo", "Nicola", Date.Parse("11/2/1980"), "Filosofia")
For Each P As Person In Ps
Console.WriteLine(P.CompleteName)
Next
È lecito assegnare oggetti Student e Teacher a una cella di un array di Person in quanto classi derivate da Person. I metodi ridefiniti,
tuttavia, rimangono e modificano il comportamento di ogni oggetto anche se richiamato da una "maschera" di classe base.
Proviamo ora con un piccolo esempio sul polimorfismo dei metodi:
Class A
Public Overridable Sub ShowText()
Console.WriteLine("A: Testo di prova")
End Sub
End Class
Class B
Inherits A
'Come si vede il metodo ha:
'- lo stesso nome: ShowText
'- lo stesso tipo: è una procedura
'- gli stessi parametri: senza parametri
'Qualunque tentativo di cambiare una di queste caratteristiche
'produrrà un errore del compilatore, che comunica di non poter
'ridefinire il metodo perchè non ne esistono di uguali nella
'classe base
Public Overrides Sub ShowText()
Console.WriteLine("B: Testo di prova")
End Sub
End Class
Ultime due precisazioni: le variabili non possono subire polimorfismo, così come i membri statici.
Shadowing
Se il polimorfismo permette di ridefinire accuratamente membri che presentano le stesse caratteristiche, ed è quindi più preciso, lo
shadowing permette letteralmente di oscurare qualsiasi membro che abbia lo stesso nome, indipendentemente dalla categoria, dalla signature e dalla
qauntità di versioni alternative presenti. La keyword da usare è Shadows, e si applica solo sul membro della classe derivata
che intendiamo ridefinire, oscurando l'omonimo nella classe base. Ad esempio:
Module Esempio
Class Base
Friend Control As Byte
End Class
Class Deriv
Inherits Base
Public Shadows Sub Control(ByVal Msg As String)
Console.WriteLine("Control, seconda versione: " & Msg)
End Sub
End Class
Sub Main()
Dim B As New Base
Dim D As New Deriv
'Entrambe le classe hanno lo stesso membro di nome
'"Control", ma nella prima è un campo friend,
'mentre nella seconda è una procedura pubblica
Console.WriteLine(B.Control)
D.Control("Ciao")
Console.ReadKey()
End Sub
End Module
Come si vede, la sintassi è come quella di Overrides: Shadows viene specificato tra lo specificatore di accesso (se c'e') e la tipologia
del membro (in questo caso Sub, procedura). Entrambe le classi presentano Control, ma la seconda ne fa un uso totalmente diverso. Ad ogni modo
l'uso dello shadowing in casi come questo è fortememente sconsigliabile: più che altro lo si usa per assicurarsi che, se mai dovesse uscire
una nuova versione della classe base con dei nuovi metodi che presentano lo stesso nome di quelli della classe derivata da noi definita, non
ci siano problemi di compatibilità.
Se una variabile è dichiarata Shadows, viene omessa la keyword Dim.