CLASSI BASE E CLASSI DERIVATE

L’ereditarietà in c sharp è il secondo pilastro della programmazione Object Oriented. Cerchiamo di chiarire il meccanismo con una figura.

Ereditarietà

In C# le classi sono le strutture dati che ci permettono di gestire le superclassi e le sottoclassi, e quindi di implementare il meccanismo dell’ereditarietà. Solo esse supportano l’ereditarietà, struct ed enum che abbiamo già visto non implementano questo meccanismo. Vediamo a livello di codice come si definisce una gerarchia di ereditarietà:

ESEMPIO DI CATENA DI EREDITARIETA’

using System;
namespace Ereditarieta
{
    class Program
    {
        static void Main(string[] args)
        {
            
        }
    }
    public class Animale
    {
        public void Respira()
        {
            Console.WriteLine("Respira");
        }
    }
    public class Mammifero : Animale
    {
       
    }
    public class Insetto : Animale
    {
        
    }
    public class Uomo : Mammifero
    {
        
    }
}

C sharpCome vedi ho definito una classe base o superclasse Animale con un metodo pubblico Respira() in quanto il respirare è una caratteristica comune a tutti gli animali. Per definire delle sottoclassi o classi derivate di Animale si dichiara una classe normalmente come siamo abituati a fare e si aggiungono due punti e il nome della superclasse. Ovviamente si possono derivare un numero qualsiasi di sottoclassi dalla classe base io ad esempio ho definito una sottoclasse Mammifero e una sottoclasse Insetto. La classe Mammifero rappresenta la superclasse per la classe derivata Uomo che deriva direttamente da Mammifero e indirettamente da Animale in quanto anche l’uomo è una specie animale. La classe derivata Uomo eredita dalla classe Animale il metodo Respira();

L’EREDITARIETA’ IN C SHARP

Come accennato l’ereditarietà è un meccanismo che ci mette a disposizione il C#, vediamo di spiegare questo meccanismo con un esempio pratico.

using System;

namespace Ereditarieta
{
    class Program
    {
        static void Main(string[] args)
        {
            Mammifero m = new Mammifero();
            m.myInt=10;
            m.myString="Ereditarietà in C#";
            m.Respira();
            m.MyProperty="Pippo";
        }
    }
    public class Animale
    {
        public int myInt;
        public string MyProperty {get;set;}
        public void Respira()
        {
            Console.WriteLine("Respira");
        }
    }
    public class Mammifero : Animale
    {
        public string myString;
        
    }
}

Come vedi ho dichiarato un numero intero myInt, il metodo Respira() e la property MyProperty nella classe base o superclasse. Creando un’istanza della classe derivata mammifero vengono automaticamente ereditati dalla classe base gli attributi, metodi e proprietà. Eventuali costruttori non vengono ereditati. Tuttavia in questo codice c’è un problema, ossia l’aver dichiarato pubblici i membri della superclasse. Per risolvere questo problema il C# ci mette a disposizione un nuovo modificatore di accesso ossia protected che estende la visibilità dei membri della superclasse solo alle classi derivate.

using System;

namespace Ereditarieta
{
    class Program
    {
        static void Main(string[] args)
        {
            
        }
    }
    public class Animale
    {
        protected int myInt;
        protected string MyProperty {get;set;}
        protected void Respira()
        {
            Console.WriteLine("Respira");
        }
    }
    public class Mammifero : Animale
    {
        protected string myString;
        
    }
}

EREDITARIETA’ IN C SHARP LE KEYWORD IS E AS

A volte si rende necessario accedere solo ai membri della superclasse, ciò lo possiamo fare utilizzando la seguente sintassi:

Animale a = new Mammifero();

La variabile a è un oggetto della classe Animale però il suo reference punta alla classe derivata Mammifero.

using System;

namespace Ereditarieta
{
    public class Program
    {
        public static void Main(string[] args)
        {

             
             Animale a = new Mammifero();//a è un oggetto della superclasse Animale,
                                         //ma il suo reference punta alla sottoclasse
                                         //Mammifero.  
             a.myInt=10;
             a.MyProperty="pippo";
             a.Respira();
             if(a is Mammifero)/*Per riportare l'oggetto al reference puntato,
                               cioè alla Classe Mammifero si possono usare
                               gli operatori is e as.
                               is è un'operatore booleano che restituisce true
                               se a è un'istanza di mammifero. In tal caso
                               si accede all'oggetto con un normale casting.*/
             {
               Mammifero m = (Mammifero)a;
               Mammifero m1 = a as Mammifero;/*Si può usare anche la keyword as
                                             in tal caso il codice risulta più pulito e leggibile*/
               m.myInt=20;
               m.MyProperty="pluto";
               m.myString="Mario";
               m.Respira();
             }
             
            
        }
    }
    public class Animale
    {
        public int myInt;
        public string MyProperty {get;set;}
        public void Respira()
        {
            Console.WriteLine("Respira");
        }
    }
    public class Mammifero : Animale
    {
        public string myString;
        
    }
}

EREDITARIETA’ E COSTRUTTORI

Definendo un costruttore nella classe derivata e lasciando il costruttore di default nella classe base la creazione di una istanza nella classe derivata avverrà senza problemi in quanto il compilatore dietro le quinte crea una istanza anche della superclasse usando il costruttore di default.

using System;

namespace Ereditarieta
{
    public class Program
    {
        public static void Main(string[] args)
        {
           Mammifero m = new Mammifero("Pippo");   
        }
    }
    public class Animale
    {
        public int myInt;
        public string MyProperty {get;set;}
        public void Respira()
        {
            Console.WriteLine("Respira");
        }
    }
    public class Mammifero : Animale
    {
        private string myString;
        public Mammifero(string myString)
        {
              this.myString=myString;
        }
        
    }
}

Nel momento in cui introduciamo un costruttore nella classe base e quindi non abbiamo più un costruttore di default ma un costruttore da noi creato, cercando di creare una istanza della classe derivata la compilazione fallirà in quanto il metodo costruttore con un parametro non è stato invocato e quindi la variabile interna myInt non è stata inizializzata. Ciò porta ad un errore del compilatore.

Vediamo come è possibile risolvere questa situazione:

using System;

namespace Ereditarieta
{
    public class Program
    {
        public static void Main(string[] args)
        {
           Mammifero m = new Mammifero("Pippo");   
        }
    }
    public class Animale
    {
        private int myInt;
        public string MyProperty {get;set;}
        public Animale(int myInt)
        {
             this.myInt=myInt;
        }
        public void Respira()
        {
            Console.WriteLine("Respira");
        }
    }
    public class Mammifero : Animale
    {
        private string myString;
        public Mammifero(string myString, int myInt):base(myInt)
        {
              this.myString=myString;
        }
        
    }
}

Come vedi per inizializzare correttamente la superclasse occorre specificare un ulteriore parametro nel costruttore della classe derivata con il quale tramite la parola chiave base seguita dal parametro viene invocato il costruttore della classe base ed inizializzata correttamente l’istanza della superclasse. base (myInt) non fa altro che invocare il costruttore della classe base.

ESERCITAZIONE SULL’EREDITARIETA IN C SHARP

Prendiamo come spunto la gestione di un conto corrente e introduciamo una nuova classe Persona in cui spostiamo le property Nome, Cognome e Denominazione dalla classe Utente alla nuova classe Persona e facciamo poi derivare Utente da Persona. L’output del programma dovrebbe rimanere invariato.

 using System;
namespace EsercitazioneEreditarieta
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Utente utente1 = new(){Id="1234560", Nome="Mario",Cognome = "Rossi",NumeroConto = "4356789"};
            utente1.Saldo = 13000m;
            Utente utente2 = new(){Id="12345678",Nome = "Mario",Cognome = "Verdi",NumeroConto = "435679879"};
            utente2.Saldo = 30000m;
            utente1.SaldoCorrente();
            utente2.SaldoCorrente();
            var b1 = new Banca("1234567890", "UBI Banca");
            var b2 = new Banca("1234576543", "UBI Banca");
            b1.Preleva(4000m,utente1);
            b2.Preleva(10000m,utente2);
            utente1.SaldoCorrente();
            utente2.SaldoCorrente();
        }
    }

    internal class Persona
    {
        public string Nome { get; set; }
        public string Cognome { get; set; }

        public string Denominazione => "Utente " + Nome + " " + Cognome;
    }

    internal class Utente : Persona
    {
        private decimal saldo;

        public string Id { get; set; }

        public decimal Saldo
        {
            get => saldo;
            set
            {
                if (value < 0)
                    Console.WriteLine($"Impossibile impostare il saldo. Valore passato: {value}");
                else
                    saldo = value;
            }
        }

        public string NumeroConto { get; set; }

        internal void SaldoCorrente()
        {
            Console.WriteLine($"Utente {Nome + " " + Cognome} saldo corrente: {Saldo}");
        }
    }

    internal class Banca
    {
        public Banca(string id, string denominazione, string abi, string cab)
        {
            Id = id;
            Denominazione = denominazione;
            Abi = abi;
            Cab = cab;
        }

        public Banca(string id, string denominazione) :
            this(id, denominazione, "06098", "14400")
        {
        }

        public string Id { get; set; }
        public string Denominazione { get; set; }
        public string Abi { get; set; }
        public string Cab { get; set; }
        public Utente Utente { get; set; }

        internal void Preleva(decimal importo,Utente utente)
        {
            this.Utente = utente;
            var saldo = Utente.Saldo;
            if (importo > saldo)
            {
                Console.WriteLine($"Operazione di prelievo non ammessa, importo richiesto {importo}");
                return;
            }

            if (importo < 0)
            {
                Console.WriteLine($"Importo prelevato non valido: {importo}");
                return;
            }

            Utente.Saldo -= importo;
            Utente = null;
        }

        internal void Accredito(decimal importo,Utente utente)
        {
            this.Utente = utente;
            if (importo < 0)
            {
                Console.WriteLine($"Importo accreditato non valido: {importo}");
                return;
            }

            Utente.Saldo += importo;
            Utente = null;
        }
    }
}

APPROFONDIMENTO SULL’EREDITARIETA’

L’ereditarietà è uno dei pilastri della programmazione orientata agli oggetti (OOP). In C#, permette di creare nuove classi che ereditano i membri di classi esistenti. Questo promuove il riuso del codice e facilita la manutenzione.

Ecco un esempio di come funziona l’ereditarietà in C#:

Esempio di base

Immaginiamo di avere una classe base Animale e una classe derivata Cane:

// Classe base
public class Animale
{
       public string Nome { get; set; }

       public void Mangia()
       {
         Console.WriteLine($”{Nome} sta mangiando.“);
       }
}

// Classe derivata
public class Cane : Animale
{
       public void Abbaia()
       {
         Console.WriteLine($”{Nome} sta abbaiando.“);
       }
}

class Program
{
      static void Main(string[] args)
      {
          Cane mioCane = new Cane();
          mioCane.Nome = “Fido“;
          mioCane.Mangia(); // Fido sta mangiando.
          mioCane.Abbia(); // Fido sta abbaiando.
      }
}

Modificatori di accesso

public: Accessibile da qualsiasi parte del programma.

private: Accessibile solo all’interno della classe in cui è definito.

protected: Accessibile all’interno della classe in cui è definito e nelle classi derivate.

internal: Accessibile solo all’interno dello stesso assembly.

Override di metodi

Le classi derivate possono sovrascrivere i metodi delle classi base usando la parola chiave override.

public class Animale
{
        public string Nome { get; set; }

        public virtual void Verso()
        {
          Console.WriteLine($”{Nome} fa un verso.“);
        }
}

public class Cane : Animale
{
        public override void Verso()
        {
          Console.WriteLine($”{Nome} abbaia.“);
        }
}

class Program
{
     static void Main(string[] args)
     {
       Cane mioCane = new Cane();
       mioCane.Nome = “Fido“;
       mioCane.Verso(); // Fido abbaia.
    }
}

Parola chiave base

La parola chiave base permette di accedere ai membri della classe base dalla classe derivata.

public class Animale
{
           public string Nome { get; set; }

           public virtual void Verso()
           {
               Console.WriteLine($”{Nome} fa un verso.“);
           }
}

public class Cane : Animale
{
          public override void Verso()
          {
               base.Verso();
               Console.WriteLine($”{Nome} abbaia.“);
          }
}

class Program
{
       static void Main(string[] args)
       {
           Cane mioCane = new Cane();
           mioCane.Nome = “Fido“;
           mioCane.Verso(); // Fido fa un verso.
                            // Fido abbaia.
       }
}

Classi astratte e metodi astratti

Una classe astratta non può essere istanziata direttamente e può contenere metodi astratti che devono essere implementati nelle classi derivate.

public abstract class Animale
{
        public string Nome { get; set; }

        public abstract void Verso();
}

public class Cane : Animale

{
     public override void Verso()
     {
          Console.WriteLine($”{Nome} abbaia.“);
     }
}

class Program
{
       static void Main(string[] args)
       {
            Cane mioCane = new Cane();
            mioCane.Nome = “Fido“;
            mioCane.Verso(); // Fido abbaia.
       }
}

LINK AI POST PRECEDENTI

LINK AL CODICE GITHUB