Gli altri pilastri della programmazione a oggetti: classi derivate, virtual e override, classi astratte, interfacce e sealed, con un esempio completo ed esercizi.
🖥️ In pratica con Windows Forms: Parco Auto 3: ereditarietà e polimorfismo.
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.
💡 Perché Sono Importanti?
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!
Ereditarietà Base - Sintassi
// 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!");
}
}
Utilizzo Ereditarietà
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
}
}
📌 Regole dell'Ereditarietà in C#
- 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
🔗 Relazione "È UN"
L'ereditarietà dovrebbe essere utilizzata quando esiste una relazione "è un" tra le classi. Un'automobile "è un" veicolo, quindi ha senso che Automobile erediti da Veicolo. In C#, una classe può ereditare da una sola classe (ereditarietà singola), ma può implementare più interfacce.
- Modificatore protected
Protected rende i membri accessibili alla classe e alle sue derivate.
Uso di protected
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
}
}
- Keyword base
Usa base per accedere a membri della classe padre.
Uso di base
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.
Polimorfismo - virtual e override
// 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}");
}
}
Polimorfismo in Azione
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
}
}
🎯 Vantaggi del Polimorfismo
Flessibilità
Tratta oggetti diversi in modo uniforme
Estensibilità
Aggiungi nuove classi senza modificare il codice esistente
Manutenibilità
Modifiche localizzate nelle classi specifiche
Codice Pulito
Evita enormi switch/if per gestire tipi diversi
Attenzione a non confondere: overloading
Esiste anche un altro meccanismo che a volte viene chiamato "polimorfismo in fase di compilazione": l'overloading. Qui non c'entra l'ereditarietà: sono più metodi nella stessa classe con lo stesso nome ma parametri diversi, e il compilatore sceglie quello giusto in base agli argomenti.
Method Overloading
public class Calcolatrice
{
// Overloading dei metodi - stesso nome, parametri diversi
public int Somma(int a, int b)
{
return a + b;
}
public double Somma(double a, double b)
{
return a + b;
}
public int Somma(int a, int b, int c)
{
return a + b + c;
}
public string Somma(string a, string b)
{
return a + b; // Concatenazione stringhe
}
}
// Utilizzo
Calcolatrice calc = new Calcolatrice();
Console.WriteLine(calc.Somma(5, 3)); // 8 (int)
Console.WriteLine(calc.Somma(5.5, 3.2)); // 8.7 (double)
Console.WriteLine(calc.Somma(5, 3, 2)); // 10 (int)
Console.WriteLine(calc.Somma("Ciao", " Mondo")); // "Ciao Mondo"
| Overloading | Override | |
|---|---|---|
| Dove | nella stessa classe | nella classe derivata |
| Firma | stesso nome, parametri diversi | stesso nome, parametri uguali |
| Parole chiave | nessuna | virtual nella base, override nella derivata |
| Chi sceglie il metodo | il compilatore, in base agli argomenti | il programma mentre gira, in base al tipo vero dell'oggetto |
| Esempio | Somma(int, int) e Somma(double, double) | Cerchio.CalcolaArea() al posto di Forma.CalcolaArea() |
⚡ virtual vs override vs new
| 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 - Sintassi
// 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");
}
}
Utilizzo Classi Astratte
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
}
}
📌 Caratteristiche Classi Astratte
- 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
Interfaccia
- Solo firma metodi (no implementazione)
- Una classe può implementare MOLTE interfacce
- Definisce "cosa" fare
Classe Astratta
- Può avere implementazioni
- Eredità singola
- Definisce "cosa" e parte del "come"
Interfacce - Sintassi
// 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");
}
}
Utilizzo Interfacce
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);
}
}
🎯 Quando Usare Interfacce
- 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
💡 Interfacce Built-in Utili
| Interfaccia | Scopo | Metodo Principale |
|---|---|---|
IComparable |
Confrontare oggetti | CompareTo() |
IEnumerable |
Iterare con foreach | GetEnumerator() |
IDisposable |
Liberare risorse | Dispose() |
IEquatable<T> |
Confronto uguaglianza | Equals() |
Bloccare l'Ereditarietà: sealed
Il modificatore sealed può essere applicato a classi o metodi. Una classe
sealed non può essere ereditata, mentre un metodo sealed non può
essere sovrascritto nelle classi derivate.
Classe e Metodo sealed
// Classe che NON può essere ereditata
public sealed class ConfigurazioneApp
{
public string NomeApp { get; set; }
public string Versione { get; set; }
// Implementazione...
}
// ❌ ERRORE - Non si può ereditare da una classe sealed
// public class MiaConfigurazione : ConfigurazioneApp { }
// Metodo sealed
public class Veicolo
{
public virtual void Avvia()
{
Console.WriteLine("Avvio veicolo...");
}
}
public class Auto : Veicolo
{
// Metodo che non può essere sovrascritto ulteriormente
public sealed override void Avvia()
{
Console.WriteLine("Avvio automobile...");
}
}
public class AutoSportiva : Auto
{
// ❌ ERRORE - Non si può fare override di un metodo sealed
// public override void Avvia() { }
}
Esempio Completo: Sistema Gestione Dipendenti
Vediamo un sistema completo che usa ereditarietà, polimorfismo, classi astratte e interfacce.
Interfacce.cs
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; }
}
Dipendente.cs - Classe Astratta Base
// 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}");
}
}
Classi Derivate.cs
// 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");
}
}
Program.cs - Test Completo
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
Console.WriteLine("=== SISTEMA GESTIONE DIPENDENTI ===\n");
// ===== CREAZIONE DIPENDENTI =====
List<Dipendente> dipendenti = new List<Dipendente>();
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})");
}
}
💡 Analisi Architettura OOP Avanzata
- 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
Esercizi Pratici
Esercizio 1: Sistema Biblioteca
Crea un sistema di gestione biblioteca con:
- Classe astratta
ElementoBibliotecacon titolo, anno - Classi derivate:
Libro(autore, pagine),Rivista(numero, mese) - Interfaccia
IPrestabilecon metodi per prestito/restituzione - Lista polimorfica di elementi
Mostra la soluzione
Soluzione Esercizio 1
using System;
using System.Collections.Generic;
public interface IPrestabile
{
bool Presta(string utente);
bool Restituisci();
bool IsDisponibile();
}
public abstract class ElementoBiblioteca
{
public string Titolo { get; set; }
public int Anno { get; set; }
public ElementoBiblioteca(string titolo, int anno)
{
Titolo = titolo;
Anno = anno;
}
public abstract string GetInfo();
}
public class Libro : ElementoBiblioteca, IPrestabile
{
public string Autore { get; set; }
public int Pagine { get; set; }
private string prestatoA = null;
public Libro(string titolo, int anno, string autore, int pagine)
: base(titolo, anno)
{
Autore = autore;
Pagine = pagine;
}
public override string GetInfo()
{
return $"Libro: {Titolo} - {Autore} ({Anno}) - {Pagine} pagine";
}
public bool Presta(string utente)
{
if (prestatoA == null)
{
prestatoA = utente;
return true;
}
return false;
}
public bool Restituisci()
{
if (prestatoA != null)
{
prestatoA = null;
return true;
}
return false;
}
public bool IsDisponibile()
{
return prestatoA == null;
}
}
public class Rivista : ElementoBiblioteca, IPrestabile
{
public int Numero { get; set; }
public string Mese { get; set; }
private string prestatoA = null;
public Rivista(string titolo, int anno, int numero, string mese)
: base(titolo, anno)
{
Numero = numero;
Mese = mese;
}
public override string GetInfo()
{
return $"Rivista: {Titolo} N.{Numero} - {Mese} {Anno}";
}
public bool Presta(string utente)
{
if (prestatoA == null)
{
prestatoA = utente;
return true;
}
return false;
}
public bool Restituisci()
{
if (prestatoA != null)
{
prestatoA = null;
return true;
}
return false;
}
public bool IsDisponibile()
{
return prestatoA == null;
}
}
class Program
{
static void Main()
{
List<ElementoBiblioteca> biblioteca = new List<ElementoBiblioteca>();
biblioteca.Add(new Libro("Il Signore degli Anelli", 1954, "Tolkien", 1200));
biblioteca.Add(new Rivista("National Geographic", 2024, 5, "Maggio"));
foreach (var elemento in biblioteca)
{
Console.WriteLine(elemento.GetInfo());
if (elemento is IPrestabile prestabile)
{
string stato = prestabile.IsDisponibile() ? "Disponibile" : "In prestito";
Console.WriteLine($"Stato: {stato}");
}
}
}
}
Riepilogo
| Concetto | Parole chiave | In una frase |
|---|---|---|
| Ereditarietà | : Base, base, protected | la classe figlia riceve proprietà e metodi del padre (relazione "è un") |
| Polimorfismo | virtual, override | stesso metodo, comportamento diverso per ogni tipo di oggetto |
| Overloading | nessuna | stesso nome, parametri diversi, nella stessa classe |
| Classe astratta | abstract | non si istanzia; obbliga le figlie a scrivere certi metodi |
| Interfaccia | interface, I... | solo un contratto; una classe ne può implementare molte |
| sealed | sealed | blocca l'ereditarietà o l'override |