MokaByte Numero 27  -  Febbraio 99  

Una Fabbrica di Oggetti

di 
Lorenzo Bettini

Come è possibile sfruttare il polimorfismo anche per la creazione di oggetti


La creazione di oggetti e dati membro all'interno di un classe può essere un problema per le classi derivate: vediamo come questo pattern semplifica e risolve le cose, come sempre in modo piuttosto elegante e riutilizzabile. Vedremo in particolare i pattern factory method ed abstract factory.


Introduzione al problema

I dati membro di una classe, come ben si sa, possono avere come tipo, a loro volta una classe, o, come si suol dire, possono essere di tipo strutturato, e non primitivo. In questi casi, spesso si legge sui libri di programmazione, che sarebbe meglio memorizzare dei puntatori, invece di oggetti veri e propri; si possono dare due motivazioni a questo consiglio (per altro molto giusto):

Questo problema non si pone in Java, in quanto dichiarando un dato membro di un tipo T non si crea un oggetto di tipo T (cioè viene semplicemente dichiarato un riferimento: si dovrà esplicitamente istanziare un tale oggetto con new T). Quindi in Java, implicitamente, i dati membro di tipo strutturato sono tutti puntatori. Effettivamente quando si deriva da una classe si ereditano tutti i suoi campi: in base al fatto che siano dichiarati come protected o private si potrà o non si potrà accedere a tali campi in modo diretto nella classe derivata, ma sarà sempre possibile accedervi tramite metodi; si tenga però presente che, nel caso siano stati dichiarati privati, si presuppone che il creatore della classe base non voglia che si possa accedere direttamente a tali dati, ma che si utilizzino semplicemente le operazioni che si possono effettuare su di essi (come delle black box, di cui non sono noti i dettagli interni). Alla base di questo tipo di ragionamento sta l'information hiding, che non vuole essere solo un mezzo per nascondere il proprio lavoro agli altri, quanto un mezzo per permettere una maggiore riusabilità: facendo assunzioni sulla struttura di certi campi di una classe, ed usandoli direttamente, si renderà quel codice esposto a possibili errori futuri, nel caso vengano modificati dei dettagli interni di tali campi; al contrario l'accesso tramite interfacce, permette di non preoccuparsi di future modifiche. Del resto questo può sembrare limitativo nel caso si crei una gerarchia di classi ben progettata che sfrutta al massimo il polimorfismo: se si deriva da una classe A si può voler specializzare il comportamento di tale classe in particolari situazioni; per far questo però si può dover specializzare anche il comportamento di certi membri.

Un esempio di problema

Consideriamo un esempio (tratto da [GOF95], e molto comune in diversi framework, come le MFC): In un framework per applicazioni si possono avere più tipi di documenti (grafici, testuali, ecc...). Le astrazioni fondamentali per questo tipo di framework sono l'applicazione ed il documento. Ovviamente di tratta di classi astratte, che il programmatore dovrà utilizzare come classi base, per scrivere le proprie applicazioni (derivando da applicazione) che faranno uso di particolari documenti (anche in questo caso derivando dalla classe astratta documento). La creazione dei documenti è un'operazione che deve essere svolta dal framework, tuttavia, per far questo, dovrebbe essere in grado di conoscere in anticipo il tipo di oggetto (di classe derivata) da creare; per altro la classe documento sarà astratta e quindi non potrà essere istanziata. Oppure si può pensare ad una classe graf che rappresenta un oggetto grafico che contiene come membro un oggetto di classe visualizzatore, che sarà utilizzato per visualizzare l'oggeto grafico sullo schermo; derivando dalla classe dell'oggetto grafico si può anche volere modificare la modalità di visualizzazione, ed in tal caso si specializzerà anche la classe visualizzatore, con una classe derivata. Nella classe derivata da graf si dovrà creare un oggetto di classe derivata da visualizzatore da assegnare al campo membro (notare che l'assegnamento è possibile perché si sono utilizzati puntatori). Supponiamo che la nostra classe base graf sia così strutturata:

public class graf {
        protected visualizzatore vis ;
        ...

        public graf() {
                vis = new visualizzatore() ;
                ...
        }
        ...

        public visualizza() { vis.visualizza() ; }
}

siamo giunti al nostro problema: nella classe derivata come fare ad istanziare un visualizzatore personalizzato (la cui classe deriva da visualizzatore)? Tale oggetto viene istanziato nel costruttore, e quindi non si ha la possibilità di crearne uno differente; una soluzione banale è quella di ricreare l'oggetto, ma questo oltre ad essere poco elegante (ed a questo qualcuno potrebbe anche non essere interessato), rischia di creare malfunzionamenti nella classe base soprattutto se si devono passare dei parametri al costruttore del visualizzatore. La cosa è ancora più complessa se non si può accedere ai parametri da passare, cioè se li può passare solo la classe base! La soluzione in questi casi è molto semplice: il pattern factory method (factory = fabbrica): nella classe base, invece di creare il visualizzatore esplicitamente, lo si crea chiamando un metodo; nella classe derivata basterà ridefinire tale metodo (eventualmente mantenendo la stessa signature, nel caso gli si debbano passare dei parametri). In questo modo nella classe derivata forniamo il metodo per creare un visualizzatore (e quindi anche uno appartenente ad una classe derivata), e sarà la classe base che al momento opportuno lo chiamerà. Vediamo nei seguenti pseudo listati l'implementazione di questa idea. La classe base diventerà:

public class graf {
        protected visualizzatore vis ;
        ...

        public graf() {
                vis = nuovoVisualizzatore() ;
                ...
        }

        protected visualizzatore nuovoVisualizzatore() {
                return new visualizzatore() ;
        }
        ...

        public visualizza() { vis.visualizza() ; }
}

dove viene definito il factory method nuovoVisualizzatore. mentre la classe derivata MIOgraf, che utilizza un visualizzatore specializzato MIOvisualizzatore (derivato ovviamente da visualizzatore), sarà:

public class MIOgraf {

        public MIOgraf() {
                ...
        }

        protected visualizzatore nuovoVisualizzatore() {
                return new MIOvisualizzatore() ;
        }
        ...
}

dove è stato ridefinito il factory method suddetto (si noti che nel costruttore non lo si invoca: lo invocherà la classe base). Si noti inoltre che non è affatto necessario ridefinire il metodo visualizza della classe base: questo si basa sul metodo omonimo del visualizzatore e grazie al polimorfismo sarà richiamata la versione giusta di tale metodo. In un certo senso la filosofia che sta dietro al pattern factory method si può riassumere informalmente in questa situazione: è come se la classe base dicesse OK: io utilizzerò un visualizzatore; le classi derivate potranno utilizzare un visualizzatore personalizzato, ed io darò loro l'opportunità di creare questo visualizzatore personalizzato, purché loro mi mettano a disposizione un metodo per fare ciò.

...più formalmente

Vediamo in modo più formale i partecipanti a questo pattern:

Abstract Factory

Una generalizzazione del precedente pattern è il pattern Abstract Factory: in questo caso si ha una classe il cui unico scopo è quello di creare degli oggetti prodotto, ed è quindi costituita solo (o almeno per la maggior parte) di factory method. Questo torna comodo quando si devono creare diversi oggetti prodotto collegati logicamente fra loro: in questo modo, invece di cospargere le varie classi che ne fanno uso di tanti factory method, si racchiudono tutti questi metodi in una classe astratta, e poi si specializza direttamente tale classe; i vari client dei prodotti utilizzeranno una particolare abstract factory per creare i propri oggetti.

 

Vi lascio adesso ad Andrea Trentini, che vi mostrerà qualche esempio concreto di implementazione di quanto visto finora.

A presto :-)

Lorenzo

Bibliografia