CodeGym /Corsi /C# SELF /Classi e metodi astratti

Classi e metodi astratti

C# SELF
Livello 22 , Lezione 1
Disponibile

1. Introduzione

Classe astratta — è una classe che non puoi semplicemente istanziare direttamente con new. Serve per creare altre classi più concrete sulla sua base. È una specie di modello o "progetto", che definisce la struttura generale e il comportamento per le classi derivate, ma lascia i dettagli dell'implementazione concreta a loro.

Ecco un'analogia per capirlo meglio: immagina di avere il concetto di "Trasporto". Nel mondo reale nessuno va in giro su un "Trasporto" astratto (a meno che non sia nella propria immaginazione). Qualcuno guida una macchina, qualcuno va in nave, qualcuno vola in aereo. La classe astratta è il tuo progetto "Trasporto", che descrive che ogni trasporto deve avere la possibilità chiave di muoversi (Move), ma come lo fa deve essere definito nelle classi figlie.


// Esempio di classe astratta
public abstract class Transport
{
    public string Model { get; set; }

    // Metodo astratto — senza implementazione
    public abstract void Move();
}

Se provi a creare un oggetto di tipo Transport, otterrai un errore di compilazione! Prova — e il compilatore ti sgriderà subito, dicendo "non puoi creare un'istanza di una classe astratta".

Quando e perché servono le classi astratte?

  • Vuoi unire caratteristiche e comportamenti comuni di oggetti diversi ma simili in un unico posto.
  • Devi impostare un "contratto unico": per esempio, "tutti gli eredi devono saper muoversi".
  • C'è un'implementazione comune di alcuni metodi, ma parte della logica deve essere specificata nelle classi derivate.

La classe astratta è uno strumento top per progettare l'architettura di un sistema grande, dove ci sono alcune "feature" comuni a tutti, ma la specificità deve essere implementata da ogni erede concreto.

2. Differenza tra classe astratta e classe normale

Qui spesso sorgono domande, quindi mettiamo tutto in chiaro!

Classe astratta vs Classe normale

  • Classe normale puoi crearla con new, può avere tutti i metodi implementati.
  • Classe astratta:
    • non puoi crearla direttamente (abstract nella dichiarazione della classe)
    • può contenere sia metodi implementati che astratti (senza corpo)
    • può avere campi, proprietà, costruttori e anche metodi statici

public abstract class Animal
{
    public string Name { get; set; }
    public void Sleep()
    {
        Console.WriteLine("L'animale dorme!");
    }

    public abstract void MakeSound();
}

3. Metodi e proprietà astratti

Metodo astratto

Un metodo astratto viene dichiarato in una classe astratta, ma non ha corpo (implementazione). La classe derivata deve implementarlo come preferisce.


public abstract class Animal
{
    public string Name { get; set; }

    // Metodo astratto
    public abstract void MakeSound();
}

Proprietà astratta

Puoi anche dichiarare una proprietà astratta, così tutti gli eredi saranno obbligati a implementarla:


public abstract class Shape
{
    // Proprietà astratta
    public abstract double Area { get; }
}

4. Ereditarietà dalle classi astratte

Una classe astratta può avere sia metodi astratti che implementati. La classe derivata deve implementare tutti i metodi e le proprietà astratti, altrimenti diventa anch'essa astratta.

Sviluppiamo la nostra "app degli animali", iniziata nelle lezioni precedenti:


public abstract class Animal
{
    public string Name { get; set; }

    public void Sleep()
    {
        Console.WriteLine($"{Name} dorme profondamente!");
    }

    public abstract void MakeSound();
}

Ora creiamo una classe normale, non astratta:


public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine($"{Name} dice: Bau!");
    }
}

Prova a non implementare MakeSound() — e vedrai un errore di compilazione.

Schema: gerarchia di classi con classe astratta


+---------------------+
|   Animal (abstract) |
+---------------------+
| Name: string        |
| + Sleep()           |
| + MakeSound()*      |
+---------------------+
         ^
         |
  +------+-------+
  |              |
 Dog            Cat

* MakeSound() — metodo astratto

5. Polimorfismo in azione: come funziona l'astrazione nella pratica

Applichiamo tutto questo nella nostra mini-app. Immagina di avere una lista di animali — diciamo cani e gatti.


List<Animal> animals = new List<Animal>
{
    new Dog { Name = "Sharik" },
    new Cat { Name = "Barsik" }
};

foreach (var animal in animals)
{
    animal.MakeSound(); // Chiamata polimorfica: ognuno fa la sua
    animal.Sleep();     // Si chiama il metodo implementato nella classe base
}

La classe Cat la implementiamo allo stesso modo:


public class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine($"{Name} dice: Miao!");
    }
}

Utilità pratica nei progetti veri

Le classi astratte sono usatissime nell'architettura di applicazioni grandi. Per esempio, nei programmi di grafica di solito c'è una classe astratta Shape con metodi tipo Draw() e proprietà tipo Area. Le figure concrete (rettangolo, cerchio) implementano questi metodi astratti con le loro formule. Grazie a questo approccio puoi creare un sacco di figure diverse senza riscrivere tutta la logica del programma.

6. Caratteristiche e limiti delle classi astratte

Non puoi creare un'istanza

Il compilatore ti vieta categoricamente di fare così:


Animal a = new Animal(); // Errore!

Solo le classi derivate (concrete) possono essere istanziate.

Le classi astratte possono contenere:

  • Dichiarazioni di metodi astratti (senza implementazione)
  • Metodi implementati (con corpo)
  • Proprietà e campi (astratti o implementati)
  • Costruttori — sì, una classe astratta può avere costruttori, ma possono essere chiamati solo dagli eredi, non direttamente dal codice che crea l'oggetto.

Le classi astratte supportano l'ereditarietà

Puoi costruire intere gerarchie di astrazioni:


public abstract class Creature
{
    public abstract void Live();
}

public abstract class Animal : Creature
{
    public abstract override void Live();
    public abstract void MakeSound();
}

Se nella catena di ereditarietà una classe non implementa tutti i metodi astratti, deve anch'essa essere dichiarata astratta!

7. Classi astratte e costruttori

Una classe astratta può avere costruttori, che verranno chiamati dai costruttori delle classi derivate.
Caratteristica importante: i costruttori delle classi astratte di solito sono dichiarati protected, per sottolineare che sono pensati solo per gli eredi, non per la creazione diretta di istanze.


public abstract class Animal
{
    public string Name { get; set; }
    
    // Costruttore dichiarato come protected
    protected Animal(string name)
    {
        Name = name;
        Console.WriteLine($"Creato animale di nome {name}");
    }
    
    public abstract void MakeSound();
}

public class Dog : Animal
{
    public Dog(string name) : base(name)
    {
        // base(name) chiama il costruttore di Animal
        Console.WriteLine("Creato cane");
    }

    public override void MakeSound()
    {
        Console.WriteLine($"{Name} dice: Bau!");
    }
}

Perché usare costruttori protected?

  • Chiarezza d'intenti: protected mostra chiaramente che il costruttore è solo per gli eredi
  • Prevenzione degli errori: anche se il compilatore vieta la creazione di istanze con new, il costruttore protected rende questa intenzione esplicita a livello di API della classe, eliminando anche la possibilità teorica di fraintendimenti
  • Buona pratica: seguire i principi di incapsulamento e API chiara

Puoi anche usare costruttori public nelle classi astratte, ma protected riflette meglio il loro scopo.

8. Confronto tra metodi astratti e virtuali

I metodi astratti sono una specie di metodi virtuali (in pratica sono sempre virtual, anche se la parola non si usa), ma non puoi scrivere l'implementazione. Se vuoi che alcuni metodi l'erede possa sovrascrivere (ma non è obbligato), usa virtual. Se deve — solo abstract.


public abstract class Worker
{
    // Tutti devono implementare
    public abstract void Work();

    // Chiunque può sovrascrivere, ma non è obbligato
    public virtual void Rest()
    {
        Console.WriteLine("Riposo standard!");
    }
}
Metodo astratto Metodo virtuale
Implementazione nella classe base No, solo la firma (dichiarazione senza corpo) Sì, c'è un'implementazione di default
Dove si può dichiarare Solo in una classe astratta In qualsiasi classe non-sealed, incluse le astratte
L'erede deve sovrascrivere Obbligatorio per il primo erede non astratto Non obbligatorio. Puoi sovrascrivere o usare la versione base
Si può chiamare direttamente No (il metodo non ha corpo) Sì (si esegue la versione base)
Scopo principale Obbligare gli eredi a fornire la propria implementazione. Definire un "contratto" Dare agli eredi la possibilità di cambiare il comportamento, se serve

9. Uso delle classi astratte nel tuo progetto

Continuiamo a sviluppare la nostra app didattica. Ora ci serve una classe astratta per gestire l'input dell'utente (tipo form diversi).


public abstract class InputForm
{
    public string Title { get; set; }

    // Metodo astratto che implementa il processo di input
    public abstract void ShowAndHandleInput();
}

public class LoginForm : InputForm
{
    public override void ShowAndHandleInput()
    {
        Console.WriteLine($"=== {Title} ===");
        Console.Write("Inserisci login: ");
        string login = Console.ReadLine();
        Console.Write("Inserisci password: ");
        string password = Console.ReadLine();
        Console.WriteLine("Autenticazione completata!");
    }
}

public class RegistrationForm : InputForm
{
    public override void ShowAndHandleInput()
    {
        Console.WriteLine($"=== {Title} ===");
        Console.Write("Scegli un login: ");
        string login = Console.ReadLine();
        Console.Write("Scegli una password: ");
        string password = Console.ReadLine();
        Console.WriteLine("Registrazione completata!");
    }
}

Utilizzo:


InputForm[] forms = new InputForm[]
{
    new LoginForm { Title = "Accesso" },
    new RegistrationForm { Title = "Registrazione" }
};

foreach (var form in forms)
{
    form.ShowAndHandleInput();
}

Questo approccio diventa sempre più potente man mano che la tua app cresce. La classe astratta è una base fantastica per creare sistemi estendibili, dove i principi DRY e SOLID (non ripeterti, scrivi modulare e facile da estendere) sono i tuoi migliori amici.

10. Errori tipici con classi e metodi astratti

Errore n°1: dimenticata l'implementazione di un metodo astratto.
Se la classe derivata non implementa tutti i metodi e le proprietà astratti, il compilatore darà errore. È una situazione comune, soprattutto lavorando con codice di altri o con grandi gerarchie. Controlla bene di non aver saltato qualcosa di importante.

Errore n°2: tentativo di rendere un metodo sia abstract che static.
Non si può fare. I metodi astratti sono pensati per essere implementati nelle istanze della classe, mentre i metodi statici non richiedono istanza. Questi due approcci si contraddicono, e il compilatore non te lo farà compilare.

Errore n°3: "pigrizia" nell'evitare di implementare tutti i membri astratti.
Se non vuoi implementare tutti i metodi astratti, dovrai rendere la classe anch'essa abstract. Non è un errore vero e proprio, ma può essere segno di un'architettura confusa. A volte ha senso mettere un'implementazione parziale in una classe astratta intermedia, per facilitare la vita ai discendenti.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION