Questo sito utilizza cookies, anche di terze parti, per mostrare pubblicità e servizi in linea con il tuo account. Leggi l'informativa sui cookies.
Username: Password: oppure
Guida al Visual Basic .NET - LEreditarieta

Guida al Visual Basic .NET

Capitolo 29° - LEreditarieta

<< Precedente Prossimo >>


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:
InheritanceExample.jpg
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.
<< Precedente Prossimo >>
A proposito dell'autore

Programmatore e analista .NET 2005/2008/2010 (in particolare C# e VB.NET), anche nell'implementazione Mono per Linux. Conoscenze approfondite di Pascal, PHP, XML, HTML 4.01/5, CSS 2.1/3, Javascript (e jQuery). Conoscenze buone di C, LUA, GML, Ruby, XNA, AJAX e Assembly 68000. Competenze basilari di C++, SQL, Hlsl, Java.