Dopo avere introdotto i concetti di base della programmazione orientata agli oggetti, come la definizione di una classe e la creazione di istanze, vediamo come possiamo implementare il meccanismo di ereditarietà delle classi in Swift.
In maniera analoga ad altri linguaggi moderni di programmazione, è possibile costruire nuove classi a partire da classi esistenti, in modo tale che la nuova possa "specializzare" o "estendere" la classe di partenza. Si fa riferimento alla classe base come superclasse o "classe padre", mentre quella creata a partire da essa è definita sottoclasse, "classe figlia" o "classe derivata". Le sottoclassi possono a loro volta essere utilizzate come classi padre per nuove sottoclassi. Una classe padre può dare origine a più sottoclassi, ma non viceversa: una sottoclasse può ereditare al più da una superclasse (il che, in altre parole, significa che Swift non supporta l'ereditarierà multipla).
Facciamo un esempio, considerando nuovamente la classe Persona
:
class Persona {
let nome: String
let cognome: String
var residenza: String
init(nome: String, cognome: String, residenza: String) {
self.nome = nome
self.cognome = cognome
self.residenza = residenza
}
func descrizione() {
print("\(nome) \(cognome) di \(residenza)")
}
}
La classe Persona
ha 3 proprietà e un metodo, nonchè un inizializzatore (tratteremo approfonditamente questo argomento nella prossima lezione).
Definiamo adesso una sottoclasse di Persona
, che permette di specializzarne le istanze con nuove proprietà. In particolare, creaiamo la classe figlia Studente
che eredita le 3 proprietà e il metodo di Persona
, aggiungendo due nuove proprietà: matricola
e corso
:
class Studente: Persona {
let matricola: Int
var corso: String
init(nome: String, cognome: String, residenza: String, matricola: Int, corso: String) {
self.corso = corso
self.matricola = matricola
super.init(nome: nome, cognome: cognome, residenza: residenza)
}
La sintassi per definire una sottoclasse richiede l'indicazione della classe padre subito dopo il nome delle nuova sottoclasse, separata da due punti.
Se creiamo un'istanza della classe Studente
:
var s = Studente(nome: "Antonio", cognome: "Calanducci", residenza: "Milano", matricola: 123456, corso: "Economia")
oltre ad accedere alle proprietà di Studente
, potremmo anche interagire con le proprietà nome
, cognome
, residenza
e chiamare il metodo descrizione()
sull'istanza di Studente
:
s.residenza = "Catania"
s.descrizione()
// Output: Antonio Calanducci di Catania
Override di metodi
A volte può essere necessario riscrivere (o fare un override di) un metodo di una sottoclasse affinchè abbia un comportamento diverso rispetto a quello della classe padre.
In Swift, non basta semplicemente, ridefinire il metodo all'interno della classe derivata, ma è necessario marcare esplicitamente la definizione del nuovo metodo con la parola riservata override
.
Supponiamo ad esempio di voler riscrivere il metodo descrizione()
della sottoclasse Studente
in modo che questo stampi, oltre nome
e cognome
, anche la sua matricola
e il corso
a cui è iscritto.
La definizione della classe Studente
diventa:
class Studente: Persona {
let matricola: Int
var corso: String
init(nome: String, cognome: String, residenza: String, matricola: Int, corso: String ) {
self.corso = corso
self.matricola = matricola
super.init(nome: nome, cognome: cognome, residenza: residenza)
}
override func descrizione() {
print("Lo studente \(nome) \(cognome) è iscritto al corso di laurea in \(corso) con matricola \(matricola)")
}
}
Se eseguiamo la seguente riga di codice:
s.descrizione()
Adesso il metodo descrizione()
restituirà in output:
Lo studente Antonio Calanducci è iscritto al corso di laurea in Economia con matricola 123456
Tuttavia ci sono casi in cui, anziché riscrivere completamente un metodo di una superclasse, vogliamo estenderne le funzionalità. È possibile infatti far sì che il metodo sovrascritto esegua il codice dello stesso metodo definito nella classe padre, attraverso l'uso della parola riservata super
. Quest'ultima ci permette di accedere a tutte le proprietà e a tutti i metodi della classe padre all'interno della classe figlia. Generalmente, la chiamata a super
viene effettuata nel corpo del metodo esteso, insieme ad altre istruzioni.
Vediamo come estendere il metodo descrizione
in modo che oltre a stampare le informazioni universitarie dello studente ci restituisca anche le informazioni generiche dell'istanza, come ad esempio la residenza. Basta aggiungere un'invocazione a super.descrizione()
dentro il corpo del metodo riscritto in Studente
:
override func descrizione() {
print("Lo studente \(nome) \(cognome) è iscritto al corso di laurea in \(corso) con matricola \(matricola)")
super.descrizione()
}
Il lettore attento avrà notato che in verità avevamo utilizzato già questo meccanismo per invocare l'inizializzatore della classe Persona
nell'inizializzatore della classe Studente
:
super.init(nome: nome, cognome: cognome, residenza: residenza)
L'unica differenza da ricordare è che, nel caso degli inizializzatori, l'inizializzatore della superclasse va eseguita dopo aver inizializzato localmente le altre proprietà locali.
Esamineremo in maniera più dettagliata inizializzatori e deinizializzatori nel prossimo articolo.