CodeGym /Corsi /C# SELF /Concetto di interfaccia e la sua sintassi

Concetto di interfaccia e la sua sintassi

C# SELF
Livello 23, Lezione 0
Disponibile

1. Cos'è un'interfaccia?

Se una classe astratta è una specie di "semilavorato" con una realizzazione parziale, allora una interfaccia è semplicemente una lista di requisiti: cosa deve saper fare una certa entità (classe) per poter essere usata in un certo contesto astratto.

Nella programmazione una interfaccia è come un "contratto" o una "lista di requisiti" per il comportamento di un oggetto. Descrive un insieme di metodi pubblici, proprietà, indicizzatori ed eventi che la classe che implementa questa interfaccia deve fornire. L'interfaccia dice: "Se vuoi chiamarti, per esempio, IDriveable (capace di guidare), allora per favore fornisci i metodi Drive() e Stop()".

Puoi immaginare un'interfaccia come una Lista di Requisiti per un Dipendente: per esempio, se vuoi assumere dei "Cuochi", nell'interfaccia ci sarà scritto: "Deve saper cucinare, servire i piatti, lavarsi le mani". Come il cuoco risolverà questi compiti — sono affari suoi. L'importante è che dall'esterno rispetti il contratto.

Importante: l'interfaccia descrive solo cosa la classe deve fornire, non come lo fa.

In breve nei termini OOP

  • Interfaccia descrive l'aspetto esterno (API) dell'oggetto: quali azioni permette di fare.
  • Non contiene stato (nessun campo). Nel senso classico le interfacce non contengono nemmeno implementazioni, ma nelle versioni moderne di C# ci sono eccezioni (tipo i metodi di default), di cui parleremo più avanti.
  • Una classe può implementare quante interfacce vuole (a differenza dell'ereditarietà delle classi!).

Analogia dalla vita reale

Porta USB — tutti sanno che puoi collegare un mouse, una tastiera, una chiavetta, delle cuffie, persino una macchina del caffè (sì, esiste anche quella!). Cosa c'è dentro il "mouse" — non importa, basta che abbia la presa USB e il dispositivo supporti il protocollo giusto. Ecco, questa è un'interfaccia!

Perché servono le interfacce?

  • Rendono il codice meno accoppiato. Il codice lavora con l'interfaccia, non con una classe concreta. Programmiamo "al livello dell'interfaccia".
  • Permettono di realizzare ereditarietà multipla del comportamento: una classe può implementare più interfacce.
  • Standardizzazione: puoi creare meccanismi universali: per esempio, tutti gli oggetti che si possono confrontare implementano l'interfaccia IComparable.
  • Testabilità: è facile sostituire una realizzazione concreta con uno "stub" nei test.
  • Pluginabilità: puoi aggiungere nuovi "moduli" senza cambiare il codice esistente.

2. Sintassi di dichiarazione di un'interfaccia

E ora — al codice! In C# un'interfaccia si dichiara con la parola chiave interface, e i nomi delle interfacce di solito iniziano con I :


// Dichiarazione di un'interfaccia
public interface IPrintable
{
    // Metodo astratto — del contratto
    void Print();

    // Si possono dichiarare anche proprietà
    string Name { get; set; }
}

Ricorda: i metodi e le proprietà dell'interfaccia NON hanno implementazione (nessun corpo del metodo, solo la firma — proprio come nei metodi astratti).

Caratteristiche chiave dell'interfaccia

  • Non puoi dichiarare campi (variabili membro) dentro un'interfaccia.
  • Tutti i metodi, proprietà, eventi e indicizzatori sono di default public (e devono esserlo anche nell'implementazione).
  • L'interfaccia non può avere costruttori (perché non ha stato).
  • L'interfaccia non dipende dal fatto che la classe che la implementa sia astratta o concreta.

Interfaccia in azione

Integra l'interfaccia nella nostra app didattica. Ora abbiamo un'interfaccia "stampabile" (IPrintable), che viene implementata dalla classe Report e, per esempio, da una nuova classe Invoice.

public interface IPrintable
{
    void Print();
    string Name { get; set; }
}

Ora definiamo una classe che implementa questa interfaccia:

public class Report : IPrintable
{
    public string Name { get; set; }

    public Report(string name)
    {
        Name = name;
    }

    // Implementazione del metodo dell'interfaccia
    public void Print()
    {
        Console.WriteLine($"Stampa del report: {Name}");
    }
}

E ora — una classe completamente diversa, ma con la stessa interfaccia:

public class Invoice : IPrintable
{
    public string Name { get; set; }

    public Invoice(string name)
    {
        Name = name;
    }

    public void Print()
    {
        Console.WriteLine($"Stampa della fattura: {Name}");
    }
}

Ora possiamo scrivere un metodo che lavora con qualsiasi entità stampabile:

public static void PrintAnything(IPrintable printable)
{
    printable.Print(); // Tutto qui! Non importa se è un report o una fattura — l'importante è che sappia stampare.
}

Ecco un esempio di utilizzo:

var report = new Report("Report mensile");
var invoice = new Invoice("Fattura #12345");

PrintAnything(report);  // Stampa del report: Report mensile
PrintAnything(invoice); // Stampa della fattura: Fattura #12345

Così le interfacce ti permettono di scrivere codice universale, estendibile e bello.

3. Implementazione delle interfacce

Collegare un'interfaccia a una classe

Un'interfaccia viene implementata da una classe usando i due punti (sì, come nell'ereditarietà):

public class Ticket : IPrintable
{
    public string Name { get; set; }
    public void Print()
    {
        Console.WriteLine($"Stampa del biglietto: {Name}");
    }
}

Importante: la classe deve implementare tutti i membri dell'interfaccia. E i membri implementati devono essere public.

Se non implementi anche solo un membro:

public class BrokenTicket : IPrintable
{
    // Implementazione di Print() mancante
    public string Name { get; set; }
}
// Errore di compilazione: 'BrokenTicket' non implementa il membro dell'interfaccia 'IPrintable.Print()'

Più interfacce

Una classe può implementare più interfacce separate da virgola:

public interface IStorable
{
    void Store();
}

public class MultiPurposeDoc : IPrintable, IStorable
{
    public string Name { get; set; }
    public void Print()
    {
        Console.WriteLine("Stampa del documento");
    }

    public void Store()
    {
        Console.WriteLine("Salvataggio del documento");
    }
}

4. Perché servono le interfacce?

Forse ora ti stai chiedendo: "Ok, la sintassi è chiara. Ma a cosa serve tutto questo nella vita reale, a parte complicarmi la vita in questo corso?" Ti assicuro che non è così! Le interfacce sono uno degli strumenti più usati nello sviluppo professionale.

Separazione delle responsabilità e basso accoppiamento (Decoupling / Loose Coupling):

  • Immagina di sviluppare un player musicale. Non gli importa da dove arriva la musica — da un file locale, da internet o da un CD. Gli interessa solo che la sorgente musicale possa fornire uno stream audio.
  • Puoi definire un'interfaccia IAudioSource con il metodo GetAudioStream().
  • Così avrai le classi FileAudioSource, InternetAudioSource, CDAudioSource, che implementano questa interfaccia.
  • Il tuo player lavorerà con IAudioSource, senza sapere il tipo concreto. Se domani arriva una nuova sorgente, tipo BluetoothAudioSource, non dovrai cambiare il codice del player! Basta creare una nuova classe che implementa IAudioSource. Questo rende il tuo sistema molto più flessibile ed estendibile. Questo è il basso accoppiamento – i componenti dipendono dalle astrazioni (interfacce), non dalle implementazioni concrete.

Polimorfismo e gestione uniforme:

Come abbiamo visto nell'esempio con PrintAnything, puoi avere un insieme di oggetti di tipi diversi, ma che condividono un comportamento comune descritto dall'interfaccia. Puoi chiamare lo stesso metodo (Print()) su tutti questi oggetti, senza sapere chi hai davanti — report, fattura o biglietto. Questo ti permette di scrivere codice molto conciso e universale.

Testing (Unit Testing):

Questa è forse una delle applicazioni più importanti delle interfacce. Quando testi un componente del tuo sistema, spesso ha bisogno di altri componenti per funzionare (ad esempio, una classe che salva dati può dipendere da una classe che lavora con il database).

Invece di passare la vera classe DatabaseSaver (che richiede un vero database per i test!), puoi passare un oggetto "finto" (o "mockato") che implementa semplicemente l'interfaccia IDataSaver. Questo oggetto "mockato" simulerà il salvataggio, senza toccare il database vero. Così puoi testare i componenti in isolamento, velocemente e senza dipendenze esterne.

Sviluppo di API e framework:

Quando crei una libreria o un framework, vuoi dare agli sviluppatori dei "punti di estensione". Le interfacce sono perfette per questo. Puoi dire: "Se vuoi che il tuo componente funzioni con il mio sistema, implementa questa interfaccia". Le librerie standard di .NET sono piene di interfacce (tipo IEnumerable<T>, IDisposable, IComparable<T>) – definiscono contratti per gli scenari più comuni.

Programmare direttamente al livello delle interfacce (Programming to an Interface):

Gli sviluppatori esperti dicono spesso: "Programma al livello delle interfacce, non delle implementazioni". Significa che quando definisci il tipo di una variabile o di un parametro di un metodo, invece di una classe concreta (Car) è meglio usare un'interfaccia (IDriveable). Questo rende il tuo codice più flessibile e meno dipendente dai dettagli di implementazione, permettendo di cambiare facilmente una realizzazione con un'altra.

5. Errori tipici lavorando con le interfacce

Errore n°1: tentativo di creare un'istanza di un'interfaccia.
Puoi scrivere Cat murzik = new Cat("Murzik", 3);, perché Cat è una classe concreta. Ma non puoi scrivere ITalkable talker = new ITalkable();. L'interfaccia è solo un contratto, un modello. Non contiene implementazione e non può essere creata direttamente. È come un progetto, non una casa finita.

Errore n°2: dimenticare di implementare tutti i membri dell'interfaccia.
Se hai dichiarato che la tua classe implementa un'interfaccia, tipo IMyInterface, allora deve implementare tutti i suoi metodi. Anche solo uno mancante causerà un errore di compilazione: MyClass non implementa IMyInterface.TheMissingMethod().

Errore n°3: modificatori di accesso sbagliati nell'implementazione.
I metodi dell'interfaccia sono implicitamente public, e anche nell'implementazione devono essere public. Se provi a fare il metodo private o protected, il compilatore darà errore. Hai promesso — implementa in modo pubblico.

Errore n°4: tentativo di aggiungere campi o costruttori nell'interfaccia.
Le interfacce descrivono il comportamento, non lo stato. Quindi non puoi aggiungere campi o costruttori. Se ci provi — errore di compilazione. Sono ammesse solo proprietà, e solo come descrizione di getter/setter.

Errore n°5: confusione tra override e implementazione dell'interfaccia.
La parola chiave override si usa per ridefinire i metodi della classe base. Ma quando implementi un'interfaccia non serve — basta scrivere il metodo public con la firma giusta. È una sfumatura importante che si dimentica facilmente.

2
Compito
C# SELF, livello 23, lezione 0
Bloccato
Dichiarazione di una semplice interfaccia e la sua implementazione
Dichiarazione di una semplice interfaccia e la sua implementazione
2
Compito
C# SELF, livello 23, lezione 0
Bloccato
Diverse classi con un'interfaccia comune
Diverse classi con un'interfaccia comune
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION