OOP Avanzata: Ereditarietà e Polimorfismo
Padroneggia i concetti avanzati della programmazione a oggetti: ereditarietà, polimorfismo, classi astratte e interfacce
🎯 Cosa Imparerai in Questa Lezione
Nella lezione precedente abbiamo visto classi, oggetti e incapsulamento. Ora esploriamo
gli altri due pilastri della OOP: ereditarietà e polimorfismo.
Questi concetti ti permetteranno di creare gerarchie di classi, riutilizzare codice e
scrivere programmi più flessibili e manutenibili.
Windows Forms usa massicciamente l’ereditarietà! Quando crei un Form,
stai creando una classe che eredita da System.Windows.Forms.Form.
Tutti i controlli (Button, TextBox, Label) ereditano da Control.
Senza capire l’ereditarietà, non puoi padroneggiare Windows Forms! 🎨
🌳 Ereditarietà (Inheritance)
L’ereditarietà permette a una classe (classe derivata o figlia) di acquisire
proprietà e metodi di un’altra classe (classe base o padre). Questo promuove il riutilizzo
del codice ed evita duplicazioni.
💡 Concetto con Esempio Reale
Pensa agli animali: tutti gli animali hanno caratteristiche comuni (nome, età, muoversi).
Ma un Cane ha anche caratteristiche specifiche (abbaia), così come un
Gatto (miagola). Invece di riscrivere nome/età per ogni animale,
creiamo una classe base Animale e facciamo ereditare Cane e Gatto!
// CLASSE BASE (o Padre o Superclasse)
public class Animale
{
// Proprietà comuni a tutti gli animali
public string Nome { get; set; }
public int Età { get; set; }
// Costruttore
public Animale(string nome, int età)
{
Nome = nome;
Età = età;
}
// Metodo comune
public void Mangia()
{
Console.WriteLine($"{Nome} sta mangiando...");
}
public virtual void FaiVerso()
{
Console.WriteLine($"{Nome} fa un verso generico");
}
}
// CLASSE DERIVATA (o Figlia o Sottoclasse)
// Sintassi: class Figlia : Padre
public class Cane : Animale
{
// Proprietà specifica del cane
public string Razza { get; set; }
// Costruttore - chiama il costruttore base con 'base'
public Cane(string nome, int età, string razza) : base(nome, età)
{
Razza = razza;
}
// Metodo specifico del cane
public void Abbaia()
{
Console.WriteLine($"{Nome}: Bau Bau!");
}
// Override del metodo base (polimorfismo!)
public override void FaiVerso()
{
Console.WriteLine($"{Nome}: Bau Bau!");
}
}
public class Gatto : Animale
{
public string Colore { get; set; }
public Gatto(string nome, int età, string colore) : base(nome, età)
{
Colore = colore;
}
public void Miagola()
{
Console.WriteLine($"{Nome}: Miao Miao!");
}
public override void FaiVerso()
{
Console.WriteLine($"{Nome}: Miao Miao!");
}
}
class Program
{
static void Main()
{
// Creo un cane
Cane cane = new Cane("Fido", 3, "Labrador");
// Proprietà ereditate da Animale
Console.WriteLine($"Nome: {cane.Nome}"); // ✅ Ereditato
Console.WriteLine($"Età: {cane.Età}"); // ✅ Ereditato
// Proprietà specifica di Cane
Console.WriteLine($"Razza: {cane.Razza}"); // ✅ Proprio di Cane
// Metodi ereditati
cane.Mangia(); // ✅ Ereditato da Animale
// Metodi specifici
cane.Abbaia(); // ✅ Proprio di Cane
cane.FaiVerso(); // ✅ Override da Animale
Console.WriteLine();
// Creo un gatto
Gatto gatto = new Gatto("Felix", 2, "Arancione");
gatto.Mangia(); // ✅ Ereditato
gatto.Miagola(); // ✅ Proprio di Gatto
gatto.FaiVerso(); // ✅ Override
}
}
- Una classe può ereditare da UNA SOLA classe base (no ereditarietà multipla)
- Usa
: NomeClasseBaseper ereditare - La classe derivata eredita tutto (eccetto costruttori e membri privati non accessibili)
- Usa
baseper chiamare il costruttore o metodi della classe padre - Usa
protectedper membri visibili solo a classe e derivate
Protected rende i membri accessibili alla classe e alle sue derivate.
public class Veicolo
{
protected string marca; // Accessibile a Veicolo e classi derivate
private string modello; // Solo Veicolo
public Veicolo(string marca, string modello)
{
this.marca = marca;
this.modello = modello;
}
protected void AvviaMotore()
{
Console.WriteLine($"{marca} - Motore avviato");
}
}
public class Auto : Veicolo
{
public Auto(string marca, string modello) : base(marca, modello)
{
}
public void Guida()
{
AvviaMotore(); // ✅ OK - protected accessibile
Console.WriteLine($"{marca} in movimento"); // ✅ OK
// Console.WriteLine(modello); // ❌ ERRORE - private non accessibile
}
}
Usa base per accedere a membri della classe padre.
public class Dipendente
{
public string Nome { get; set; }
public decimal StipendioBase { get; set; }
public Dipendente(string nome, decimal stipendio)
{
Nome = nome;
StipendioBase = stipendio;
}
public virtual decimal CalcolaStipendio()
{
return StipendioBase;
}
}
public class Manager : Dipendente
{
public decimal Bonus { get; set; }
// Costruttore: chiama base per inizializzare Nome e StipendioBase
public Manager(string nome, decimal stipendio, decimal bonus)
: base(nome, stipendio) // ← Chiama costruttore padre
{
Bonus = bonus;
}
public override decimal CalcolaStipendio()
{
// Usa base per chiamare metodo padre
return base.CalcolaStipendio() + Bonus;
}
}
// Utilizzo
Manager m = new Manager("Anna", 3000, 500);
Console.WriteLine($"Stipendio: {m.CalcolaStipendio()}"); // 3500
🎭 Polimorfismo
Il polimorfismo permette a oggetti di classi diverse di rispondere
allo stesso metodo in modo diverso. In C# si ottiene con virtual e override.
💡 Polimorfismo in Pratica
Tutti gli animali possono “fare un verso”, ma ogni animale lo fa in modo diverso:
il cane abbaia, il gatto miagola, il cavallo nitrisce. Stesso metodo (FaiVerso()),
comportamenti diversi! Questo è polimorfismo.
// CLASSE BASE
public class Forma
{
public string Nome { get; set; }
// virtual = può essere sovrascritto nelle classi derivate
public virtual double CalcolaArea()
{
return 0;
}
public virtual void Disegna()
{
Console.WriteLine($"Disegno una forma generica: {Nome}");
}
}
// CLASSE DERIVATA 1
public class Cerchio : Forma
{
public double Raggio { get; set; }
public Cerchio(double raggio)
{
Nome = "Cerchio";
Raggio = raggio;
}
// override = sovrascrive il metodo della classe base
public override double CalcolaArea()
{
return Math.PI * Raggio * Raggio;
}
public override void Disegna()
{
Console.WriteLine($"Disegno un cerchio con raggio {Raggio}");
}
}
// CLASSE DERIVATA 2
public class Rettangolo : Forma
{
public double Base { get; set; }
public double Altezza { get; set; }
public Rettangolo(double b, double h)
{
Nome = "Rettangolo";
Base = b;
Altezza = h;
}
public override double CalcolaArea()
{
return Base * Altezza;
}
public override void Disegna()
{
Console.WriteLine($"Disegno un rettangolo {Base}x{Altezza}");
}
}
class Program
{
static void Main()
{
// ===== POLIMORFISMO =====
// Array di Forma, ma contiene oggetti di classi derivate!
Forma[] forme = new Forma[3];
forme[0] = new Cerchio(5);
forme[1] = new Rettangolo(4, 6);
forme[2] = new Cerchio(3);
Console.WriteLine("=== CALCOLO AREE ===");
// Stesso metodo, comportamenti diversi!
foreach (Forma forma in forme)
{
forma.Disegna(); // Chiama il metodo override specifico!
Console.WriteLine($"Area: {forma.CalcolaArea():F2}");
Console.WriteLine();
}
// Output:
// Disegno un cerchio con raggio 5
// Area: 78.54
//
// Disegno un rettangolo 4x6
// Area: 24.00
//
// Disegno un cerchio con raggio 3
// Area: 28.27
}
}
Tratta oggetti diversi in modo uniforme
Aggiungi nuove classi senza modificare il codice esistente
Modifiche localizzate nelle classi specifiche
Evita enormi switch/if per gestire tipi diversi
| Keyword | Dove si usa | Significato |
|---|---|---|
virtual |
Classe base | Il metodo può essere sovrascritto nelle classi derivate |
override |
Classe derivata | Sovrascrive un metodo virtual della classe base |
new |
Classe derivata | Nasconde (non sovrascrive) un metodo della classe base |
🔷 Classi Astratte
Una classe astratta è una classe che non può essere istanziata
direttamente. Serve come base per altre classi e può contenere metodi astratti
(senza implementazione) che le classi derivate devono implementare.
💡 Quando Usare Classi Astratte
Usa classi astratte quando vuoi definire un “contratto” con alcune parti già implementate.
Ad esempio: tutti i veicoli hanno un motore e si muovono, ma il modo di muoversi
è diverso (auto su strada, aereo in cielo). La classe Veicolo può essere astratta!
// CLASSE ASTRATTA
public abstract class Veicolo
{
// Proprietà normali
public string Marca { get; set; }
public string Modello { get; set; }
// Costruttore (viene chiamato dalle classi derivate)
public Veicolo(string marca, string modello)
{
Marca = marca;
Modello = modello;
}
// Metodo normale (con implementazione)
public void MostraInfo()
{
Console.WriteLine($"{Marca} {Modello}");
}
// Metodo ASTRATTO (senza implementazione)
// Le classi derivate DEVONO implementarlo
public abstract void Muovi();
// Metodo virtual (opzionale sovrascriverlo)
public virtual void Ferma()
{
Console.WriteLine("Veicolo fermato");
}
}
// CLASSE DERIVATA 1
public class Auto : Veicolo
{
public Auto(string marca, string modello) : base(marca, modello)
{
}
// OBBLIGATORIO implementare metodo astratto
public override void Muovi()
{
Console.WriteLine($"{Marca} {Modello} si muove su strada");
}
}
// CLASSE DERIVATA 2
public class Aereo : Veicolo
{
public int Altitudine { get; set; }
public Aereo(string marca, string modello) : base(marca, modello)
{
}
// OBBLIGATORIO implementare metodo astratto
public override void Muovi()
{
Console.WriteLine($"{Marca} {Modello} vola a {Altitudine}m");
}
// Override opzionale
public override void Ferma()
{
Console.WriteLine($"{Marca} {Modello} atterra");
}
}
class Program
{
static void Main()
{
// Veicolo v = new Veicolo("Tesla", "Model 3");
// ❌ ERRORE! Non puoi istanziare classi astratte
// ✅ OK - Istanzio classi derivate
Auto auto = new Auto("Tesla", "Model 3");
Aereo aereo = new Aereo("Boeing", "747");
aereo.Altitudine = 10000;
// Polimorfismo con classi astratte
Veicolo[] veicoli = new Veicolo[] { auto, aereo };
foreach (Veicolo v in veicoli)
{
v.MostraInfo();
v.Muovi(); // Chiama implementazione specifica!
v.Ferma();
Console.WriteLine();
}
// Output:
// Tesla Model 3
// Tesla Model 3 si muove su strada
// Veicolo fermato
//
// Boeing 747
// Boeing 747 vola a 10000m
// Boeing 747 atterra
}
}
- Dichiarate con keyword
abstract - Non possono essere istanziate con
new - Possono avere metodi astratti (senza corpo) e normali (con corpo)
- I metodi astratti devono essere implementati nelle classi derivate
- Possono avere costruttori (chiamati dalle classi derivate)
- Possono avere campi, proprietà, metodi normali
🔌 Interfacce
Un’interfaccia è un contratto che definisce quali metodi e proprietà
una classe deve implementare, ma senza fornire l’implementazione.
Le interfacce permettono il polimorfismo senza ereditarietà.
💡 Interfacce vs Classi Astratte
- Solo firma metodi (no implementazione)
- Una classe può implementare MOLTE interfacce
- Definisce “cosa” fare
- Può avere implementazioni
- Eredità singola
- Definisce “cosa” e parte del “come”
// INTERFACCIA
// Convenzione: nome inizia con I
public interface IVolante
{
// Solo firme - NO implementazione!
void Vola();
void Atterra();
int AltitudineMax { get; set; } // Proprietà
}
public interface INuotante
{
void Nuota();
void Immergiti(int profondità);
}
// Classe che implementa UN'interfaccia
public class Aereo : IVolante
{
public int AltitudineMax { get; set; }
// DEVE implementare tutti i membri dell'interfaccia
public void Vola()
{
Console.WriteLine("Aereo decolla e vola");
}
public void Atterra()
{
Console.WriteLine("Aereo atterra");
}
}
// Classe che implementa DUE interfacce!
public class Anatra : IVolante, INuotante
{
public int AltitudineMax { get; set; }
// Implementa IVolante
public void Vola()
{
Console.WriteLine("Anatra vola basso");
}
public void Atterra()
{
Console.WriteLine("Anatra si posa");
}
// Implementa INuotante
public void Nuota()
{
Console.WriteLine("Anatra nuota in superficie");
}
public void Immergiti(int profondità)
{
Console.WriteLine($"Anatra si immerge a {profondità}cm");
}
}
class Program
{
static void Main()
{
Aereo aereo = new Aereo { AltitudineMax = 12000 };
Anatra anatra = new Anatra { AltitudineMax = 100 };
// ===== POLIMORFISMO CON INTERFACCE =====
// Array di IVolante (interfaccia!)
IVolante[] volanti = new IVolante[] { aereo, anatra };
Console.WriteLine("=== COSE CHE VOLANO ===");
foreach (IVolante v in volanti)
{
v.Vola();
Console.WriteLine($"Altitudine max: {v.AltitudineMax}m");
}
Console.WriteLine("\n=== COSE CHE NUOTANO ===");
// Solo l'anatra implementa INuotante
INuotante nuotatore = anatra;
nuotatore.Nuota();
nuotatore.Immergiti(50);
}
}
- Vuoi definire un contratto senza implementazione
- Diverse classi non correlate devono avere gli stessi metodi
- Serve implementare multiple interfacce (no ereditarietà multipla)
- Esempio:
IComparable,IEnumerable,IDisposable
| Interfaccia | Scopo | Metodo Principale |
|---|---|---|
IComparable |
Confrontare oggetti | CompareTo() |
IEnumerable |
Iterare con foreach | GetEnumerator() |
IDisposable |
Liberare risorse | Dispose() |
IEquatable<T> |
Confronto uguaglianza | Equals() |
🚀 Esempio Completo: Sistema Gestione Dipendenti
Vediamo un sistema completo che usa ereditarietà, polimorfismo, classi astratte e interfacce.
using System;
// Interfaccia per entità che possono lavorare
public interface ILavoratore
{
void Lavora();
decimal CalcolaStipendio();
}
// Interfaccia per entità che possono gestire altri
public interface IManager
{
void GestisciTeam();
int NumeroSottoposti { get; set; }
}
// Classe astratta base
public abstract class Dipendente : ILavoratore
{
public string Nome { get; set; }
public string Cognome { get; set; }
public string Matricola { get; set; }
protected decimal stipendioBase;
public Dipendente(string nome, string cognome, string matricola, decimal stipendio)
{
Nome = nome;
Cognome = cognome;
Matricola = matricola;
stipendioBase = stipendio;
}
// Metodo normale
public string GetNomeCompleto()
{
return $"{Nome} {Cognome}";
}
// Metodo astratto - ogni tipo di dipendente lavora diversamente
public abstract void Lavora();
// Metodo virtual - può essere sovrascritto
public virtual decimal CalcolaStipendio()
{
return stipendioBase;
}
public virtual void MostraInfo()
{
Console.WriteLine($"📋 {GetNomeCompleto()} ({Matricola})");
Console.WriteLine($" Stipendio: €{CalcolaStipendio():N2}");
}
}
// DIPENDENTE OPERATIVO
public class DipendenteOperativo : Dipendente
{
public string Reparto { get; set; }
public DipendenteOperativo(string nome, string cognome, string matricola,
decimal stipendio, string reparto)
: base(nome, cognome, matricola, stipendio)
{
Reparto = reparto;
}
public override void Lavora()
{
Console.WriteLine($"{GetNomeCompleto()} lavora nel reparto {Reparto}");
}
public override void MostraInfo()
{
base.MostraInfo();
Console.WriteLine($" Reparto: {Reparto}");
}
}
// MANAGER (Dipendente + IManager)
public class ManagerReparto : Dipendente, IManager
{
public decimal Bonus { get; set; }
public int NumeroSottoposti { get; set; }
public ManagerReparto(string nome, string cognome, string matricola,
decimal stipendio, decimal bonus)
: base(nome, cognome, matricola, stipendio)
{
Bonus = bonus;
NumeroSottoposti = 0;
}
public override void Lavora()
{
Console.WriteLine($"{GetNomeCompleto()} coordina il team");
GestisciTeam();
}
public void GestisciTeam()
{
Console.WriteLine($" Gestisce {NumeroSottoposti} dipendenti");
}
public override decimal CalcolaStipendio()
{
return base.CalcolaStipendio() + Bonus;
}
public override void MostraInfo()
{
base.MostraInfo();
Console.WriteLine($" Bonus: €{Bonus:N2}");
Console.WriteLine($" Team: {NumeroSottoposti} persone");
}
}
// TECNICO (competenze specifiche)
public class Tecnico : Dipendente
{
public string Specializzazione { get; set; }
public int AnniEsperienza { get; set; }
public Tecnico(string nome, string cognome, string matricola,
decimal stipendio, string specializzazione, int anni)
: base(nome, cognome, matricola, stipendio)
{
Specializzazione = specializzazione;
AnniEsperienza = anni;
}
public override void Lavora()
{
Console.WriteLine($"{GetNomeCompleto()} lavora come {Specializzazione}");
}
public override decimal CalcolaStipendio()
{
// Bonus per esperienza: €50 per anno
decimal bonusEsperienza = AnniEsperienza * 50;
return base.CalcolaStipendio() + bonusEsperienza;
}
public override void MostraInfo()
{
base.MostraInfo();
Console.WriteLine($" Specializzazione: {Specializzazione}");
Console.WriteLine($" Esperienza: {AnniEsperienza} anni");
}
}
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
Console.WriteLine("=== SISTEMA GESTIONE DIPENDENTI ===\n");
// ===== CREAZIONE DIPENDENTI =====
List dipendenti = new List();
DipendenteOperativo op1 = new DipendenteOperativo(
"Mario", "Rossi", "D001", 1500, "Produzione"
);
DipendenteOperativo op2 = new DipendenteOperativo(
"Luigi", "Verdi", "D002", 1500, "Logistica"
);
Tecnico tec1 = new Tecnico(
"Anna", "Bianchi", "T001", 2000, "Elettronica", 5
);
ManagerReparto mgr1 = new ManagerReparto(
"Paolo", "Neri", "M001", 3000, 500
);
mgr1.NumeroSottoposti = 3;
dipendenti.Add(op1);
dipendenti.Add(op2);
dipendenti.Add(tec1);
dipendenti.Add(mgr1);
// ===== POLIMORFISMO IN AZIONE =====
Console.WriteLine("--- ELENCO DIPENDENTI ---\n");
foreach (Dipendente d in dipendenti)
{
d.MostraInfo(); // Chiama versione specifica!
d.Lavora(); // Metodo astratto implementato
Console.WriteLine();
}
// ===== CALCOLO STIPENDI TOTALI =====
decimal totaleStipendi = 0;
foreach (Dipendente d in dipendenti)
{
totaleStipendi += d.CalcolaStipendio();
}
Console.WriteLine($"💰 Totale stipendi: €{totaleStipendi:N2}");
// ===== FILTRAGGIO PER INTERFACCIA =====
Console.WriteLine("\n--- MANAGER ---");
foreach (Dipendente d in dipendenti)
{
// Verifica se implementa IManager
if (d is IManager manager)
{
Console.WriteLine($"{d.GetNomeCompleto()}:");
manager.GestisciTeam();
}
}
// ===== STATISTICHE =====
Console.WriteLine("\n--- STATISTICHE ---");
Console.WriteLine($"Totale dipendenti: {dipendenti.Count}");
Console.WriteLine($"Stipendio medio: €{totaleStipendi / dipendenti.Count:N2}");
// Trova stipendio massimo
decimal maxStipendio = 0;
string nomeMassimo = "";
foreach (Dipendente d in dipendenti)
{
decimal stipendio = d.CalcolaStipendio();
if (stipendio > maxStipendio)
{
maxStipendio = stipendio;
nomeMassimo = d.GetNomeCompleto();
}
}
Console.WriteLine($"Stipendio più alto: {nomeMassimo} (€{maxStipendio:N2})");
}
}
- Classe astratta Dipendente: Base comune con metodo astratto
Lavora() - Ereditarietà: 3 classi derivate con implementazioni specifiche
- Polimorfismo: Stesso array, oggetti diversi, comportamenti diversi
- Interfacce:
ILavoratoreeIManagerper contratti - Override:
CalcolaStipendio()personalizzato per ogni tipo - Casting con is: Verifica se oggetto implementa interfaccia
- Riutilizzo codice
- Gerarchie di classi
- Keyword:
: Base,base - Modificatore
protected
virtualeoverride- Stesso metodo, comportamenti diversi
- Flessibilità runtime
- Non istanziabili
- Metodi astratti obbligatori
- Contratto + implementazione parziale
- Solo contratto (no implementazione)
- Implementazioni multiple
- Polimorfismo senza ereditarietà
Lezione 14 del Corso C# Completo