Quando abbiamo parlato di interfacce abbiamo detto che si tratta di un modo per definire tipi personalizzati in TypeScript. Anche per le classi possiamo definire delle interfacce, ma il loro significato è un po' diverso dal concetto di tipo, anche se per certi versi potremmo ricondurlo all'approccio strutturale del type system di TypeScript.
Come nella maggior parte dei linguaggi orientati agli oggetti, una interfaccia è un vincolo al rispetto di un contratto, cioè della modalità con cui un oggetto può interagire con un altro oggetto. Una interfaccia definisce i membri che una classe deve avere, in modo tale gli oggetti che intendono interagire con le loro istanze sappiano quali proprietà e metodi chiamare.
Proviamo a chiarire il concetto con un esempio. Supponiamo di avere la seguente interfaccia:
interface IPersona {
nome: string;
cognome: string;
visualizzaNomeCognome():string;
}
Essa stabilisce che una classe che voglia aderire al suo contratto, deve avere le proprietà nome
e cognome
e un metodo visualizzaNomeCognome()
che restituisce una stringa.
La seguente classe è un esempio di quella che viene generalmente chiamata una implementazione dell'interfaccia IPersona
:
class Persona implements IPersona {
nome: string;
cognome: string;
constructor(nome: string, cognome: string) {
this.nome = nome;
this.cognome = cognome;
}
visualizzaNomeCognome() {
return this.nome + " " + this.cognome;
}
}
In questo caso la classe Persona
dichiara di implementare l'interfaccia IPersona
tramite la parola chiave implements. Anche la seguente classe implementa la stessa interfaccia:
enum Materie { Storia, Informatica, Matematica, Scienze };
class Studente implements IPersona {
nome: string;
cognome: string;
materie: Materie[];
constructor(nome: string, cognome: string) {
this.nome = nome;
this.cognome = cognome;
this.materie = [];
}
visualizzaNomeCognome() {
return this.nome + ' ' + this.cognome;
}
}
In questo caso la classe Studente
implementa una proprietà non prevista dall'interfaccia, la proprietà materie
. Ciò è del tutto lecito, in quanto un'interfaccia definisce i membri minimi che una classe deve implementare, ma la classe può implementare anche altri membri.
È anche possibile per una classe implementare più interfacce contemporaneamente, come mostrato dal seguente esempio:
interface IStudio {
materie: Materie[];
}
class Studente implements IPersona, IStudio {
nome: string;
cognome: string;
materie: Materie[];
constructor(nome: string, cognome: string) {
this.nome = nome;
this.cognome = cognome;
this.materie = [];
}
visualizzaNomeCognome() {
return this.nome + ' ' + this.cognome;
}
}
TypeScript prevede anche l'estendibilità delle interfacce, cioè un meccanismo con cui è possibile definire una nuova interfaccia ereditando le caratteristiche definite da un'altra interfaccia. Ad esempio, il seguente codice definisce l'interfaccia IStudente
a partire dall'interfaccia IPersona
:
interface IStudente extends IPersona {
materie: Materie[];
}
Con questa interfaccia a disposizione possiamo definire la classe Studente
nel seguente modo:
class Studente implements IStudente {
nome: string;
cognome: string;
materie: Materie[];
constructor(nome: string, cognome: string) {
this.nome = nome;
this.cognome = cognome;
this.materie = [];
}
visualizzaNomeCognome() {
return this.nome + ' ' + this.cognome;
}
}