Nell’articolo odierno parliamo di due concetti molto importanti per la programmazione ad oggetti: Ereditarietà e Polimorfismo

EREDITARIETA’

Il concetto di ereditarietà è molto importante nella programmazione ad oggetti. Esso ci consente di creare nuove classi che riutilizzano, estendono e modificano il comportamento già definito in altre classi. La classe i cui membri sono ereditati è chiamata classe di base, mentre la classe che eredita dalla classe base viene chiamata classe derivata.

Concettualmente, una classe derivata è una specializzazione della classe base. Ad esempio, nel grafico qui sopra riportato, possiamo notare di come la classe base Animale venga estesa da due classi derivate: la classe Uccello e la classe Mammifero. Per quanto sia i mammiferi che gli uccelli siano degli animali, le loro diversità li fanno specializzare in rami differenti.

In C#, per derivare da una classe base, usiamo una notazione molto sintetica, ovvero:

Per cui, utilizzando i due punti, vediamo come sia possibile estendere la classe base Animale alle classi derivate Mammifero e Uccello.

Qui sopra, possiamo vedere un altro esempio di ereditarietà. La classe Point2D, la quale rappresenta concettualmente le coordinate cartesiane di un punto sugli assi X e Y, viene estesa dalla classe Point3D. Quest’ultima, eredita dalla classe di base le proprietà ed i metodi di Point2D, aggiungendone, però, una nuova: ovvero la coordinata Z, consentendoci così di rappresentare il punto in questione in uno spazio tridimensionale.

Qui abbiamo riportato un altro esempio con la classe di base Person, rappresentante il concetto di persona, che, in questo caso, viene derivata da due classi: Student e Teacher. Quest’ultime ereditano le info presenti in Person e ci aggiungono ulteriori properties, ridefiniscono alcuni metodi e ne aggiungono di nuovi, specifici dell’implementazione.

Quest’ultimo esempio introduce un concetto molto importante e fondamentale, ovvero la sovrascrittura dei metodi. Nella fattispecie, possiamo vedere come nella classe Person ci siano due metodi, SayHi() e DoWork(), che vengono sovrascritti dalle sottoclassi Student e Teacher.

Affinché un metodo possa essere sovrascritto da una classe, è necessario dichiararlo nella classe di base con la keyword virtual. Così facendo, la sottoclasse in questione avrà la possibilità di ridefinirlo a seconda delle necessità. Se invece, al posto di virtual, una classe di base dichiara un metodo o una proprietà come abstract, allora quel metodo o quella proprietà dovranno per forza essere sovrascritti dalla classe derivata.

È anche possibile dichiarare una classe come abstract, nel caso si desideri impedire l’istanza diretta della classe. Questa classe, per cui, conterrà solo metodi abstract, ovvero dei metodi che presentano soltanto la firma del metodo ma non l’implementazione. Qui di seguito viene riportato un esempio di dichiarazione:

Così facendo, le classi che estendono Example, avranno l’obbligo di implementare il metodo DoSomething, pertanto, un utilizzatore, non potrà istanziare direttamente Example perché abstract, ma anzi, potrà istanziare solo le sottoclassi derivate da quest’ultima.

Una classe può anche impedire, tramite la keyword sealed, di essere ereditata da altre classi. Qui di seguito, un esempio di dichiarazione:

POLIMORFISMO

Il poliformismo è anch’esso, assieme all’ereditarietà, un concetto di estrema importanza quando si parla di programmazione ad oggetti. Fondamentalmente, il polimorfismo si basa su due elementi, ovvero:

  • Durante la fase di runtime, gli oggetti di una classe derivata possono essere trattati come oggetti di una classe di base.
  • Le classi di base possono definire e implementare metodi virtual, e le classi derivate possono sostituirli facendo l’override dei metodi. Ciò significa che, dalla classe base, viene messo a disposizione un metodo che, se sovrascritto dalla classe derivata, a runtime verrà sostituito con il metodo presente in quest’ultima.

Qui di seguito, troviamo un esempio di come può essere applicato il polimorfismo:

In questo caso, la classe Shape, contiene un metodo Draw() dichiarato come virtual, potenzialmente sovrascrivibile da delle classi derivate, le quali possono ridefinirlo a seconda delle necessità:

Come possiamo notare, ogni classe ha eseguito l’override del metodo in questione, ridefinendolo con una propria stringa di testo. Per cui, se a runtime dovessi ritrovarmi con tre oggetti distinti: Circle, Rectangle e Triangle; nel momento in cui chiamerò il metodo Draw() ogni oggetto mi stamperà a schermo una stringa diversa, proprio perché ne è stata eseguita la sovrascrittura.

Il materiale è tratto dalle lezioni IPID svolte da Marco Pirruccio di Heartwood Labs/Operaludica.