Menu
Coddy logo textTech

Interfaccia o classe astratta

Fa parte della sezione Programmazione orientata agli oggetti del percorso C# di Coddy. Lezione 26 di 70.

Sia le interfacce che le classi astratte ti permettono di definire contratti che le altre classi devono rispettare, ma hanno scopi diversi e funzionalità distinte.

Un'interfaccia definisce cosa deve fare una classe, senza alcuna implementazione. È un puro contratto:

public interface IFlyable
{
    void Fly();
}

public class Bird : IFlyable
{
    public void Fly()
    {
        Console.WriteLine("Flapping wings");
    }
}

Una classe astratta può fornire sia un contratto sia un’implementazione condivisa. È un progetto parziale:

public abstract class Vehicle
{
    public string Brand { get; set; }
    
    public void StartEngine()
    {
        Console.WriteLine("Engine started");
    }
    
    public abstract void Drive();  // Deve essere implementato
}

public class Car : Vehicle
{
    public override void Drive()
    {
        Console.WriteLine("Driving on roads");
    }
}

Le differenze principali riguardano le regole di ereditarietà e lo scopo.

Una classe può implementare più interfacce, ma ereditare da una sola classe astratta.

Usa un'interfaccia quando classi non correlate devono condividere un comportamento (come IFlyable per gli uccelli e gli aerei). Usa una classe astratta quando classi correlate condividono codice e stato comuni (come i veicoli che condividono un meccanismo di avviamento del motore).

Le classi astratte possono avere costruttori, campi e modificatori di accesso sui membri. Tradizionalmente, le interfacce non potevano averli, anche se le versioni moderne di C# consentono implementazioni predefinite.

In caso di dubbio, preferisci le interfacce per una maggiore flessibilità: potrai sempre aggiungere in seguito una classe astratta se diventa necessaria un'implementazione condivisa.

challenge icon

Sfida

Facile

Costruiamo un sistema di lettore multimediale che mostri quando usare le interfacce invece delle classi astratte. Creerai una struttura in cui dispositivi non correlati condividono il comportamento di riproduzione tramite un’interfaccia, mentre tipi di contenuti multimediali correlati condividono un’implementazione comune tramite una classe astratta.

Organizzerai il codice in quattro file:

  • IPlayable.cs: Definisci un’interfaccia chiamata IPlayable nello spazio dei nomi Media. Questa interfaccia rappresenta il contratto per qualsiasi elemento in grado di riprodurre contenuti multimediali: che si tratti di un telefono, di una TV o di un lettore musicale dedicato. Deve dichiarare un unico metodo Play() che restituisce una stringa.
  • MediaFile.cs: Definisci una classe astratta chiamata MediaFile nello spazio dei nomi Media. Rappresenta la base condivisa per tutti i file multimediali. Deve avere una proprietà Title (string) e una proprietà Duration (int, che rappresenta i secondi). Includi un costruttore che imposti entrambi i valori. Aggiungi un metodo concreto chiamato GetInfo() che restituisce "{Title} ({Duration}s)". Dichiara anche un metodo astratto GetType() che restituisce una stringa: ogni tipo di contenuto multimediale definirà quale tipo di file rappresenta.
  • AudioFile.cs: Definisci una classe AudioFile nello spazio dei nomi Media che eredita da MediaFile e implementa IPlayable. Aggiungi una proprietà Artist (string). Il costruttore deve accettare titolo, durata e artista. Esegui l’override di GetType() affinché restituisca "Audio". Implementa Play() affinché restituisca "Playing audio: {Title} by {Artist}".
  • Program.cs: Nel file principale, crea un AudioFile usando i valori di input. Mostra entrambi gli aspetti della classe: chiama GetInfo() e GetType() (ereditati dalla classe astratta), quindi chiama Play() (dall’interfaccia). Assegna il file audio a una variabile IPlayable e chiama di nuovo Play() per mostrare il polimorfismo dell’interfaccia.

Riceverai tre input:

  • Titolo del brano
  • Durata in secondi
  • Nome dell’artista

Stampa l’output in questo formato:

Info: {GetInfo() result}
Type: {GetType() result}
Direct: {Play() result}
Via Interface: {Play() result from IPlayable variable}

Per esempio, se gli input sono Bohemian Rhapsody, 354 e Queen, l’output dovrebbe essere:

Info: Bohemian Rhapsody (354s)
Type: Audio
Direct: Playing audio: Bohemian Rhapsody by Queen
Via Interface: Playing audio: Bohemian Rhapsody by Queen

Nota come AudioFile tragga vantaggio da entrambi gli approcci: eredita l’implementazione condivisa (GetInfo()) dalla classe astratta e al tempo stesso soddisfa il contratto IPlayable. La classe astratta consente il riutilizzo del codice per tipi di contenuti multimediali correlati, mentre l’interfaccia permette a qualsiasi classe non correlata di essere «riproducibile» senza condividere una gerarchia di ereditarietà.

Provalo tu

using System;
using Media;

class Program
{
    public static void Main(string[] args)
    {
        // Leggi i valori di input
        string title = Console.ReadLine();
        int duration = Convert.ToInt32(Console.ReadLine());
        string artist = Console.ReadLine();
        
        // TODO: Crea un AudioFile usando i valori di input
        
        // TODO: Stampa Info: seguito dal risultato di GetInfo()
        
        // TODO: Stampa Type: seguito dal risultato di GetType()
        
        // TODO: Stampa Direct: seguito dal risultato di Play()
        
        // TODO: Memorizza il file audio in una variabile IPlayable
        
        // TODO: Stampa Via Interface: seguito dal risultato di Play() della variabile IPlayable
    }
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore C# online