C#

Classi abstract e sealed

Il modificatore abstract implica che l'entità alla quale è applicato ha una implementazione incompleta. Si può utilizzare su metodi, proprietà ecc.. ma in questo paragrafo lo vedremo applicato alle classi.
Una classe dichiarata abstract può essere utilizzata solo come base per altre classi, in modo del tutto simile alle interfacce. In altre parole una classe abstract non può essere istanziata direttamente (anche se, esiste forse un trucco sporco in questo senso ma davvero non ne vale la pena). L'implementazione dei suoi membri dichiarati abstract è a carico della classe da essa derivata.

  Esempio 25.1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
15
17
18
using System;

abstract class A
{
  public int q()
  {
    return 5 * 5;
  }
}


class program
{
  public static void Main()
  {
    A a = new A();
  }
}

Questo è un classico esempio di base, che, come è facile capire, non funziona, nemmeno compila; il motivo è evidente, alla riga 15 cerchiamo di istanziare direttamente dalla classe A che è dichiarata abstract e quindi non si può. Il compilatore si esprime chiaramente:

error CS0144: Non è possibile creare un'istanza della classe o dell'interfaccia astratta 'A'

Se vogliamo che le cose vadano a posto, salvo che il programma non fa nulla, salvo stampare a video il numero 25, ecco il codice:

  Esempio 25.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
15
17
18
19
20
21
22
23
using System;

abstract class A
{
  public int q()
  {
    return 5 * 5;
  }
}

class B : A
{

}

class program
{
  public static void Main()
  {
    B b = new B();
    Console.WriteLine(b.q());
  }
}

Alla riga 20 istanziamo correttamente la classe B definita alla 11 come implementazione della classe astratta A. Possiamo utilizzare il metodo q in quanto esso non è definito abstract.
Come terzo esempio vediamo:

  Esempio 25.3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
15
17
18
19
20
21
22
23
using System;

abstract class A
{
  abstract public int q();
}

class B : A
{
  public override int q()
  {
    return 5 * 5;
  }
}

class program
{
  public static void Main()
  {
    B b = new B();
    Console.WriteLine(b.q());
  }
}

Nella classe astratta A il metodo q viene dichiarato a sua volta abstract e quindi non può avere una sua implementazione interna che sarebbe del tutto inutile perchè, come detto, un metodo così definito deve essere sviluppato nell'ambito della classe derivata. L'implementazione avviene a partire dalla riga 10 fino alla 13, come si capisce, e prevede la keyword override che si utilizza appunto per estendere entità dichiarate astratte (oltre che quando viene usato virtual). Un membro può essere definito astratto solo nell'ambito di una classe astratta. Un membro abstract non può anche essere marcato come virtual, non avrebbe senso dal momento che deve per forza essere sottoposto ad un override.
Le classi astratte possono avere dei costruttori, condivisibili con le classi derivate. Peraltro non è permesso rendere pubblici i costruttori delle classi astratte, cosa logica se si pensa all'impossibilità di istanziazione delle classi oggetto di questo paragrafo. I costruttori in parola quindi dovrebbero essere marcati come protected. In effetti, in assenza di costruttori, il compilatore aggiunge quello di deafult firmato come protected.

Che utlità hanno le classi astratte? Evidentemente in piccole applicazioni come quelle che presento non ne hanno. Ma la loro natura è orientata verso progetti più grandi, dove possono essere usate per creare dei template di classi da implementarsi a piacere pur potendo presentare funzionalità complete comuni. Insomma una sorta di base di classi che possono interessare un intero progetto facilitando la costruzione di elementi con caratteristiche comuni. Come vedremo, questo è simile al discorso delle interfacce, laddove queste non possono avere alcuna implementazione di base ma sono derivabili in forma multipla, al contrario delle classi astratte che, essendo appunto classi, non consentono l'ereditarietà multipla. la discussione interfacce vs classi astratte è molto attiva in rete e potrete trovare molte dotte opinioni sull'argomento che qui non è il caso di trattare.

L'altro interessante modificatore di classe di cui parliamo oggi è costituito dalla keyword sealed. Una classe marcata tramite esso non può essere ereditata. Come si intuisce subito, una classe astratta non può essere anche sealed, altrimenti è inutilizzabile. Una classe sealed per il resto è istanziabile normalmente. Per quanto detto il seguente esempio:

  Esempio 25.4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
15
17
18
19
20
21
22
using System;

sealed class A
{
  public int q()
  {
    return 5 * 5;
  }
}

class B : A
{
}

class program
{
  public static void Main()
  {
    B b = new B();
    Console.WriteLine(b.q());
  }
}

non compila

error CS0509: 'B' non può derivare dal tipo sealed 'A'

chiaro? Invece:

  Esempio 25.5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
15
17
18
using System;

sealed class A
{
  public int q()
  {
    return 5 * 5;
  }
}

class program
{
  public static void Main()
  {
    A a = new A();
    Console.WriteLine(a.q());
  }
}

Questo funziona. Una classe sealed può ereditare da altre classi senza problemi così come le classi astratte.
A cosa serve una classe sealed? Di solito a "blindarne" le funzionalità, ovvero impedire che esse siano in qualche modo modificate attraverso la creazione di altre classi da essa discendenti.

Concludo questo paragrafo con una tabella che può tornare utile:

Classe/comportamento puo' ereditare può essere ereditata può essere istanziata
classe normale si si si
classe abstract si si no
classe sealed si no si
classe static no no no