78 TS – Classi astratte vs Interfaccie

Una classe astratta è una classe che implementa lo scheletro strutturale di un metodo, ma lascia il compito di ultimare e implementare questo metodo alle classi che la estendono.

Le classi figlie avranno obbligatoriamente i metodi stanziati nella classe astratta dalla quale discendono e avranno il compito di ultimare tali metodi.

Per definire una classe astratta è necessario utilizzare la keyword abstract prima dell’attributo class e definiamo il metodo astratto che vogliamo implementare nelle classi discendenti.

abstract class Logger {
    abstract log(msg : string) : void;
}

In presenza di un metodo abstract definirò necessariamente anche la classe come abstract.

La classe astratta può tranquillamente avere metodi interni non astratti

abstract class Logger {
    abstract log(msg : string) : void
    
    generateId() : number {
        return Math.round(Math.random()*1000000);
     }
}

La classe astratta non può essere stanziata direttamente da un oggetto, ma deve essere estesa da una classe discendente

class ConsolLogger extends Logger {

}

Già l’editor ci avvisa che essendo un’estensione di una classe astratta, deve obbligatoriamente implementare gli stessi metodi astratti

abstract class Logger {
    abstract log(msg : string) : void

    generateId() : number {
        return Math.round(Math.random()*1000000);
     }
}

class ConsolLogger extends Logger {
    log(msg: string): void {

    }
}

let log = new ConsolLogger();

In questo caso, nonostante non abbia un risultato, abbiamo implementato una struttura corretta del codice, perchè abbiamo definito classe e metodo astratto, abbiamo definito la classe che li estende, abbiamo definito un oggetto costruito sulla classe concreta estensione di quella astratta.

Aggiungiamo dei console nel metodo e nell’oggetto per testare

abstract class Logger {
    abstract log(msg : string) : void

    generateId() : number {
        return Math.round(Math.random()*1000000);
     }
}

class ConsolLogger extends Logger {
    log(msg: string): void {
        console.log(msg);
    }
}

let Clog = new ConsolLogger();
Clog.log('login to console');

L’oggetto come la classe reale ha accesso automaticamente anche al metodo concreto della classe astratta

console.log(Clog.generateId());

Quindi quando usare una classe astratta e quando un’interfaccia?

Un’ interface ci serve solo quando dobbiamo stabilire le proprietà e i metodi che una classe deve avere senza alcuna implementazione.

La classe abstract può avere metodi già implementati e lasciare la loro definizione particolare alla classe discendente.

Ovviamente una classe può estendere una classe astratta ed implementare anche un’interface:

interface Log {
    msg: string
    id: number
}

ora estendiamo la classe astratta ed implementiamo Log

class ConsolLogger extends Logger implements Log {
    msg : string = '';
    id : number = 0;
    log(msg: string): void {
        console.log(msg);
    }
}

volendo potremmo anche creare un costruttore che inizializza queste proprietà .

Possiamo inserire anche un metodo nell’interfaccia, ma non completalo

interface Log {
    msg: string;
    id: number;
    getMsg() : string;
    ShowMsg() : string {
        console.log(this.msg);
    }
}

Errore ShowMsg non può avere risultati in un’interfaccia, mentre getMsg() è corretto perchè solo dichiarato, quindi lo implemento e risolvo nella classe figlia

class ConsolLogger extends Logger implements Log {
    msg : string = '';
    id : number = 0;
    log(msg: string): void {
        console.log(msg);
    }
    getMsg(): string {
        return this.msg();
    }
}

Riassumendo

Nella classe abstract posso definire ed implementare metodi, nell’interfaccia posso solo creare lo schema definendo solo nomi e tipi di proprietà e metodi.