Articoli Manifesto Tools Links Canali Libri Contatti ?
Design / OOP

Il problema della fragilità della classe base

Abstract
In questo breve articolo viene esposto un problema di design a volte sottovalutato ma di notevole impatto in termini di flessibilità e manutenzione di un prodotto: il problema della fragilità della classe base. La problematica verrà affrontata in relazione al linguaggio Java anche se la sua discussione è relativa ad un cardine dell'Object Orientation: l'ereditarietà!
Data di stesura: 31/12/2002
Data di pubblicazione: 02/01/2003
Ultima modifica: 04/04/2006
di Stefano Fago Discuti sul forum   Stampa

Oggetti ed Ereditarietà

Uno degli elementi che distinguono un linguaggio ad oggetti da quelli strutturati è sicuramente la possibilità di ereditare da ancestor delle caratteristiche e comportamenti. L'ereditarietà è un meccanismo che ha decretato un grande passo in avanti nel modo di modellare i problemi, derivarne un design e promuovere il riuso. Questo concetto, infatti, ha condotto ad uno stile di programmazione indicato come programmazione per differenza dato che si può far evolvere un sistema con la crescita di una gerarchia di classi che sono in un rapporto di generalizzazione/specializzazione.
Uno degli esempi classici è quello legato all'idea di automobile; in Java possiamo codificare una classe Auto che descrive gli aspetti più generici di un automobile, le caratteristiche assolutamente comuni a tutti i modelli. Si può pensare ad esempio al fatto che un'automobile sia munita di motore.
Il concetto di automobile sportiva può concretizzarsi come specializzazione della classe Auto, visto che anche un'auto sportiva possiede un motore. Nella nuova classe AutoSportiva non sarà necessario riscrivere codice per la gestione del concetto Motore in quanto già presente nella classe Auto ed ereditato dalla stessa. Applicando questo modo di vedere anche alla classe Motore abbiamo la possibilità di esprimere nella classe automobile le logiche di base per la gestione del motore e nelle classi specializzate l'adozione di motori differenti; tutto questo senza dover cambiare una riga di codice.
[Figura 1]

Figura 1

Nella pratica di ogni giorno ereditarietà e polimorfismo sono spesso in simbiosi e forniscono una coppia potentissima di tool per il design ad oggetti. Questi elementi, però, inducono a problemi sensibili specie se non si segue un'attenta disciplina o si eccede nel loro uso.

Tipi di Ereditarietà

I linguaggi ad oggetti hanno un supporto differente per l'ereditarietà: in alcuni è attuabile l'ereditarietà multipla in altri, come Java, è possibile solo l'ereditarietà singola. Nel primo caso è possibile avere più classi ancestor da cui ereditare mentre nel secondo vi può essere una sola root- class. È presente anche un altro tipo di classificazione per la quale è possibile distinguere tra ereditarietà d'interfaccia, o subtyping, ed ereditarietà implementativa, o subclassing. La differenza tra le due tipologie di ereditarietà nasce dalla differenza che esiste tra il concetto di tipo e quello di classe. I due elementi sembrerebbero coincidere ma in realtà non è così: un tipo esprime un protocollo che può essere realizzato in modi differenti; una classe esprime invece un comportamento ben preciso. In Java in concetto di tipo è espresso dall'idioma interface che permette di esprimere un'astrazione senza che sia definita una data implementazione. Il concetto di classe, invece, combina i due elementi, tipo ed implementazione, dato che codificare una nuova classe implica esprimere un nuovo tipo che ha già implementato il relativo comportamento.
L'ereditarietà d'interfaccia intende esprimere la possibilità di trasmette alle specializzazioni gli elementi del protocollo dettato dall'ancestor a differenza dell'ereditarietà implementativa dove ciò che viene ereditato è il comportamento.

Class Fragility

Il subtyping è considerata una caratteristica abbastanza tranquilla, senza molte contro-indicazioni a differenza delle problematiche introdotte dal subclassing che è il motivo della nostra discussione.
Sappiamo che uno dei cardini dell'object orientation è l'Incapsulamento che permette di abbinare strutture dati ed operazioni negli oggetti e che insieme all'Information Hiding permette di rendere privato ciò che è specifico di una data astrazione. L'ereditarietà implementativa viene a rompere questa idea d'incapsulamento dato che, nella gerarchia, nel rapporto tra generalizzazione- specializzazione, vengono ad essere esposti elementi privati dell'elemento da cui si eredita. Questo è il presupposto per il problema della fragilità della classe base. Tale problematica emerge, infatti, quando si attuano cambiamenti nelle funzionalità di una superclasse interessando a catena anche il comportamento delle sottoclassi, le quali terminano di operare correttamente.
Riprendiamo l'esempio della classe Auto; possiamo pensare a del codice nella forma seguente:
public class Auto
{
  protected Motore motore = new Motore();
  protected boolean motoreAcceso;
.
.
.
  public void accensione()
  {
    if (!motoreAcceso) {
      motore.attiva();
      motoreAcceso = true;
    }
  }
.
.
.
}
Possiamo considerare che la sottoclasse AutoSportiva erediti dalla superclasse la funzionalità di accendere il motore e che ne presenti una versione specializzata:
public class AutoSportiva extends Auto
{
.
.
.
  public void accensione()
  {
    if (controlloImpiantoElettrico() && controlloFluidi())
      super.accensione(); 
  }
.
.
.
}
La problematica in discussione può presentarsi allorché le sottoclassi di Auto vedano alterata la funzionalità della superclasse; nel nostro esempio se il metodo accensione cambiasse, non è più garantito il corretto funzionamento della classe AutoSportiva. Potremmo, ancora, pensare ad una classe Ferrari che specializza la classe AutoSportiva: se il metodo controllaImpiantoElettrico() non restituisse più un booleano l'errore sarebbe ancora più evidente, specie in fase di compilazione! Un ulteriore esempio della problematica in esame si evidenzierebbe se la variabile motore divenisse di un'altro tipo, differente da Motore, rendendo le assunzioni delle sottoclassi a riguardo non più valide.

Evitare la fragilità della classe base

Evitare questa problematica non è semplicissimo e richiede comunque molta attenzione; linee guida possono essere le seguenti:
  • Usare librerie stabili: potrebbe sembrare banale ma, l'uso di elementi standard è un primo passo verso la stabilità in modo che il problema non sorga;
  • Usare sempre attributi privati e fornire getter e setter per accedervi: in questo modo si rafforza l'incapsulamento anche in presenza di ereditarietà e si ottiene un design più pulito;
  • Usare programmazione difensiva nel caso di attributi complessi; questo come corollario al precedente punto;
  • Programmare per interfacce non per classi. Un classico adagio della programmazione ad oggetti in questo caso quanto mai utile: le interfacce non forniscono implementazione, quindi, non presentano il problema;
  • Rendere i metodi ereditabili final: in questo modo non può verificarsi l'overriding di metodi, azione che potrebbe essere ulteriore causa di fragilità;
  • Evitare l'ereditarietà. Il riuso del codice è importante ma, non deve spingere alla creazioni di gerarchie dalle dimensioni sensibili o volute per il solo riuso di implementazioni;
  • Non eliminare bensì deprecare. Gli sviluppatori Java spesso incontrano il concetto di deprecato e in questa sede ne vediamo l'utilità. Eliminare un pezzo di classe vuol dire, in modo ricorsivo, alterare l'equilibrio funzionale nella gerarchia che vede la data classe come root-class. Indicando come non più usabile (deprecato) un dato elemento si fornisce l'opportuno mezzo per non alterare rapporti preesistenti tra generalizzazioni-specializzazioni e, grazie ad una giusta documentazione, si può condurre lo sviluppatore verso funzionalità nuove o sostitutive.

Informazioni sull'autore

Stefano Fago, classe 1973. Diplomato in ragioneria, ha conseguito il Diploma di Laurea in Informatica con un progetto legato alle interfacce grafiche soft-realtime in Java. Dopo esperienze in Alcatel ed Elea, ha svolto attività di consulenza come Software Developer e Trainer alla ObjectWay S.p.A. sede di Milano. Attualmente Software Designer presso la sezione Innovazione e Attività Progettuali di BPU Banca. Appassionato del linguaggio Java e di tutte le tecnolgie Object Oriented. Polistrumentista dilettante.

È possibile consultare l'elenco degli articoli scritti da Stefano Fago.

Altri articoli sul tema Design / OOP.

Discuti sul forum   Stampa

Cosa ne pensi di questo articolo?

Discussioni

Questo articolo o l'argomento ti ha interessato? Parliamone.