In informatica l'ereditarietà è uno dei concetti fondamentali nel paradigma di programmazione a oggetti. Essa consiste in una relazione che il linguaggio di programmazione, o il programmatore stesso, stabilisce tra due classi. Se la classe B eredita dalla classe A, si dice che B è una sottoclasse di A e che A è una superclasse di B. Denominazioni alternative equivalenti, sono classe padre, classe madre o classe base per A e classe figlia o classe derivata per B. A seconda del linguaggio di programmazione, l'ereditarietà può essere ereditarietà singola o semplice (ogni classe può avere al più una superclasse diretta) o multipla (ogni classe può avere più superclassi dirette).
In generale, l'uso dell'ereditarietà dà luogo a una gerarchia di classi; nei linguaggi con ereditarietà singola, si ha un albero se esiste una superclasse "radice" di cui tutte le altre classi sono direttamente o indirettamente sottoclassi (come la classe nel caso di Java) o a una foresta altrimenti; l'ereditarietà multipla definisce invece una gerarchia a grafo aciclico diretto.
Interpretazione L'ereditarietà è una relazione di generalizzazione/specializzazione: la superclasse definisce un concetto generale e la sottoclasse rappresenta una variante specifica di tale concetto generale. Su questa interpretazione si basa tutta la teoria dell'ereditarietà nei linguaggi a oggetti. Oltre a essere un importante strumento di modellazione (e quindi significativo anche in contesti diversi dalla programmazione in senso stretto, per esempio in UML), l'ereditarietà ha importantissime ripercussioni sulla riusabilità del software.
Relazione is-a Per esempio, data una classe telefono, se ne potrebbe derivare la sottoclasse cellulare, poiché il cellulare è un caso particolare di telefono. Questo tipo di relazione viene detta anche relazione is-a (): "un cellulare è-un telefono".
La relazione is-a che deve legare una sottoclasse alla sua superclasse viene spesso esplicitata facendo riferimento al cosiddetto principio di sostituzione di Liskov, introdotto nel 1993 da Barbara Liskov e Jeannette Wing. Secondo questo principio, gli oggetti appartenenti a una sottoclasse devono essere in grado di esibire tutti i comportamenti e le proprietà esibiti da quelli appartenenti alla superclasse, in modo tale che usarli in luogo di questi ultimi non alteri la correttezza delle informazioni restituite dal programma. Affinché la classe cellulare possa essere concepita come sottoclasse di telefono, per esempio, occorre che un cellulare soddisfi tutte le caratteristiche che definiscono genericamente un telefono.
Tanto la relazione is-a quanto il principio di Liskov non richiedono che la sottoclasse esponga solo le caratteristiche esibite dalla superclasse, ma che esponga almeno tali caratteristiche. Per esempio, il fatto che un cellulare possa anche inviare SMS non inficia il fatto che esso sia sostituibile a un telefono. Pertanto, la sottoclasse può esibire caratteristiche aggiuntive rispetto alla superclasse. Una sottoclasse può anche eseguire in maniera differente rispetto alla superclasse alcune delle sue funzionalità, a patto che questa differenza non sia osservabile dall'esterno. Per esempio, un cellulare svolge le telefonate in modo tecnicamente diverso rispetto a un telefono tradizionale (utilizzando la rete GSM), ma dal punto di vista esteriore si ottiene lo stesso risultato (eseguire una telefonata), pertanto ciò non contraddice il principio di sostituzione.
Violazione del principio di sostituibilità Nonostante tutto, in genere è tecnicamente possibile estendere una classe violando il principio di sostituibilità, in quanto le regole imposte dal linguaggio di programmazione in uso non possono andare oltre la correttezza formale del codice scritto ed eventualmente la sua aderenza a determinate precondizioni o postcondizioni. In certi casi, il principio viene violato intenzionalmenteEsempio in Java: la classe java.util.IdentityHashMap, appartenente alle librerie standard del linguaggio, viola intenzionalmente il contratto generale stabilito dal tipo java.util.Map, ma, come si vede dalla documentazione della stessa, il fatto che il contratto generale dell'interfaccia Map sia violato è ben documentato.; quando succede è opportuno che si documenti la cosa in modo appropriato, onde evitare che le istanze della classe siano usate dove è necessario che sia valido il citato principio di sostituibilità.
Polimorfismo
Quando il principio di sostituibilità è rispettato, l'ereditarietà può essere utilizzata per ottenere il cosiddetto polimorfismo. Se ben usato, esso permette di avere programmi flessibili, nel senso che permette di scrivere codice in grado di far fronte a necessità e modifiche future richiedendo correzioni minime e/o ben circoscritte.
Definizione tecnica Il modo in cui i linguaggi di programmazione gestiscono le relazioni di ereditarietà consegue dal significato dato all'ereditarietà come relazione is-a. Una classe B dichiarata come sottoclasse di una superclasse A: eredita (ha implicitamente) tutte le variabili di istanza e tutti i metodi di A. può avere (non necessariamente) variabili o metodi aggiuntivi. può ridefinire i metodi ereditati da A attraverso l'overriding, in modo che essi eseguano la stessa operazione concettuale in un modo specializzato.
Il fatto che la sottoclasse erediti tutte le caratteristiche della superclasse ha senso proprio alla luce del concetto di sostituibilità. Nel paradigma object-oriented, infatti, una classe di oggetti risulta definita dalle sue caratteristiche (attributi e metodi). Di conseguenza, sarebbe falso affermare che "un cellulare è un telefono" se il cellulare non avesse tutte le caratteristiche che definiscono un telefono (per esempio un microfono, un altoparlante e la possibilità di inoltrare e ricevere telefonate).
Quanto detto non implica, tuttavia, che la sostituibilità sia garantita: la relazione classe-sottoclasse deve essere concettualmente distinta dalla relazione tipo-sottotipo. In particolare, il meccanismo di overriding non garantisce che la semantica del metodo della superclasse resti inalterata nella sottoclasse. La sostituibilità non viene inoltre rispettata quando si utilizzano strumenti per l'occultamento di visibilità dei metodi (limitation).
Applicazioni dell'ereditarietà L'ereditarietà può essere studiata e descritta da diversi punti di vista: comportamento degli oggetti rispetto all'ambiente esterno; struttura interna degli oggetti; gerarchia dei livelli di ereditarietà; impatto dell'ereditarietà sull'ingegneria del software. In linea di massima, per evitare confusione, è consigliabile affrontare separatamente questi aspetti
Specializzazione
Uno dei maggiori vantaggi dell'ereditarietà è la possibilità di creare versioni "specializzate" di classi già esistenti, cioè di crearne dei sottotipi. I costrutti che consentono di realizzare l'ereditarietà non garantiscono la specializzazione, a cui deve provvedere il programmatore definendo la sottoclasse nella maniera opportuna, in modo da rispettare il principio di sostituibilità.
Un altro meccanismo simile alla specializzazione è la specificazione: si ha quando una classe ereditata dichiara di possedere un determinato "comportamento" senza però implementarlo effettivamente: si parla in questo caso di classe astratta. Tutte le classi "concrete" (cioè non a loro volta astratte) che ereditano da questa classe astratta devono obbligatoriamente implementare quel particolare comportamento "mancante".
Ridefinizione
Molti linguaggi di programmazione a oggetti permettono a una classe o a un oggetto di modificare il modo in cui è implementata una propria funzionalità ereditata da un'altra classe (di solito un metodo). Questa caratteristica è chiamata "ridefinizione" o con il termine inglese overriding. A fronte di overriding, lo stesso metodo avrà un comportamento diverso se invocato sugli oggetti della superclasse o in quelli della sottoclasse (per lo meno nel caso dei linguaggi che adottano il binding dinamico). Ad esempio, data una classe Quadrilatero che definisce alcuni comportamenti generali per tutte le figure geometriche con 4 lati, la sottoclasse Rettangolo potrebbe ridefinire (ovvero "fare overriding di") quei metodi di Quadrilatero che possono essere reimplementati in maniera più specifica tenendo conto delle specificità dei rettangoli (per esempio, il calcolo del perimetro potrebbe essere riscritto nella classe Rettangolo come doppio della somma della lunghezza delle due basi, anziché come semplice somma delle lunghezze dei lati).
Estensione Un'altra ragione per usare l'ereditarietà è fornire a una classe dati o funzionalità aggiuntive. Questa operazione è di solito chiamata estensione oppure subclassing. A differenza del caso della specializzazione prima esposto, con l'estensione nuovi dati o funzionalità sono aggiunti alla classe ereditata, accessibili e utilizzabili da tutte le istanze della classe. L'estensione viene usata spesso quando non è possibile o conveniente aggiungere nuove funzionalità alla classe base. La stessa operazione può essere eseguita anche a livello di oggetto — anziché di classe — ad esempio usando i cosiddetti decorator pattern.
Riutilizzo del codice
Uno dei principali vantaggi dell'uso dell'ereditarietà (in particolare combinata col polimorfismo) è il fatto di favorire il riuso di codice. Non solo una sottoclasse eredita (e quindi riusa) il codice della superclasse: il polimorfismo garantisce anche che tutto il codice precedentemente scritto per manipolare oggetti della superclasse sia anche implicitamente in grado di manipolare oggetti della sottoclasse. Per esempio, un programma che sia in grado di rappresentare graficamente oggetti di classe Quadrilatero non avrebbe bisogno di alcuna modifica per trattare analogamente anche oggetti di una eventuale classe Rettangolo.
Esempi Supponiamo che in un programma si usi una classe Animale contenente dati per specificare, ad esempio, se l'animale è vivo, il luogo in cui si trova, quante zampe ha, ecc.; in aggiunta a questi dati la classe potrebbe contenere anche metodi per descrivere come l'animale mangia, beve, si muove, si accoppia, ecc. Se si volesse creare una classe Mammifero molte di queste caratteristiche rimarrebbero esattamente le stesse di quelle dei generici animali, ma alcune sarebbero diverse. Diremmo quindi che Mammifero è una sottoclasse della classe Animale (oppure, inversamente, che Animale è la classe base — chiamata anche classe genitrice — di Mammifero). La cosa importante da notare è che nel definire la nuova classe non è necessario specificare nuovamente che un mammifero ha le normali caratteristiche di un animale (luogo in cui si trova, il fatto che mangia, beve, ecc), ma basta aggiungere le caratteristiche peculiari che contraddistinguono i mammiferi rispetto agli altri animali (ad esempio che ha le mammelle) e ridefinire le funzioni che, pur essendo comuni a tutti gli altri animali, si manifestano in modo diverso, ad esempio il modo di riprodursi. Nell'esempio che segue, scritto in Java, notare all'interno del metodo riproduciti() la chiamata a super.riproduciti(), che è un metodo della classe base che si sta ridefinendo. Per usare parole semplici si potrebbe dire che questo metodo dice di "fare prima tutto quello che la classe base farebbe" seguito poi dal codice che indica quali sono le "cose in più" che deve fare la nuova classe.
Java class Mammifero extends Animale { Pelo pelo; Mammelle mammelle;
Mammifero riproduciti() { Mammifero prole;
super.riproduciti(); if(isFemmina()) { prole = super.partorisci(); prole.allatta(m_b);
curaCuccioli(prole); return prole;