Articoli Manifesto Tools Links Canali Libri Contatti ?
Design / Design Patterns

Strange Patterns

Abstract
I G.O.F. Design Pattern sono il tool di design più adottato dai developer ad oggetti e su di loro è possibile riscontrare una vasta letteratura.
In questo articolo verranno introdotti alcuni Design Pattern scarsamente presentati dalla stampa specializzata ma di grande utilità nello sviluppo di ogni giorno!
Data di stesura: 24/01/2005
Data di pubblicazione: 03/02/2005
Ultima modifica: 04/04/2006
di Stefano Fago Discuti sul forum   Stampa

Null Object Pattern

Il design in questione propone l'adozione di un oggetto che in relazione ad un dato tipo esprima il concetto di "nessuna azione" o "elemento vuoto", rispettando il contratto (l'interfaccia) del tipo di cui è surrogato. La problematica che si vuol risolvere è quella di evitare l'uso di NULL in situazioni in cui è necessario restituire, ad un dato Client, un riferimento ad un oggetto, anche se non deve verificarsi alcuna elaborazione.

Quanto esposto può risultare strano ma vi sono situazioni in cui questo design risulta utile. Supponiamo, ad esempio, che in conseguenza di una data elaborazione debba essere restituita una lista di elementi: cosa accade se il risultato è vuoto? Vediamo del codice.

Ipotizziamo che in un gruppo aziendale si abbiano differenti società catalogate in base alla nazione ed area geografica di competenza, possiamo pensare ad un gestore delle informazioni delle differenti unità che, tra gli altri, presenta un metodo di ricerca:

  1. class UnitsInfoManager 
  2.   InfoList getOperativeUnitFor(String nationCode, String areaCode) 
  3.     ... 
  4.     // ...azione di ricerca... 
  5.     ... 
  6.     return ??? 

L'invocazione della ricerca potrebbe produrre un risultato vuoto, cioè nessuna unità presente nella nazione ed area geografica richiesti; si noti il fatto che la ricerca è andata a buon fine, ma banalmente il risultato è pari ad un elenco senza elementi...

A questo punto si potrebbe banalmente ritornare NULL; adottando questa soluzione, però, obblighiamo il Client dell'astrazione UnitsInfoManager a verificare se il riferimento restituito è valido onde evitare fastidiose eccezioni:

  1. InfoList availableUnits =  manager.getOperativeUnitFor(...); 
  2.  
  3. if (availableUnits != null) { 
  4.   for (Iterator iterator = availableUnits.iterator(); iterator.hasNext();) { 
  5.     // fai qualcosa per ogni unità... 

È possibile rendere il codice più robusto adottando, invece, un Null Object come risultato della ricerca cioè una lista vuota:

  1. InfoList getOperativeUnitFor(String nationCode, String areaCode) 
  2. {   
  3.   ...  
  4.   InfoList result = new EmptyInfoList(); 
  5.   // ...azione di ricerca...   
  6.   ... 
  7.   return result; 

Il Client a questo punto non deve più attuare verifiche e non c'è il rischio di NullPointerException a runtime!

  1. List availableUnits =  manager.getOperativeUnitFor(...); 
  2.  
  3. for (Iterator iterator = availableUnits.iterator(); iterator.hasNext();) { 
  4.   // fai qualcosa per ogni unità... 

Vediamo ora un altro esempio che pone l'accento sull'idea di "nessuna azione"!

Supponiamo che per alcune elaborazione del sistema informatico del gruppo aziendale sia necessario convertire i risultati economici di società di altre nazioni nella valuta della capogruppo. La conversione può non essere banale e per questo motivo è stata sviluppata una gerarchia, per ereditarietà, di oggetti che incapsulano l'algoritmo di conversione in base alla nazione di provenienza dei dati. Questi oggetti verranno estratti grazie ad un apposito metodo di ricerca:

  1. ConversionPolicy getCurrencyConversionPolicyFor(Locale locale){ ... } 

Cosa accade se la nazione di provenienza dei dati è quella in cui opera la capogruppo? È evidente che non devono verificarsi conversioni. Si ponga nuovamente attenzione al fatto che, anche in questa situazione, non siamo di fronte ad una condizione di errore ma ad una normale ramo dell'elaborazione in cui non bisogna far nulla!

Se restituissimo NULL obbligheremmo nuovamente lo sviluppatore ad attuare dei controlli che si possono evitare inserendo nella gerarchia delle ConversionPolicy un oggetto del tipo NullConversionPolicy.

Encapsulated Context Pattern

In sistemi complessi diventa necessario poter usufruire di dati comuni, con visibilità globale, come nel caso di dati di configurazione. L'uso di elementi globali è una pratica sconsigliata nei sistemi ad oggetti ed anche se il GOF Pattern Singleton può offrire una via di uscita il suo abuso può risultare deleterio.

Obiettivo del pattern, target di questo paragrafo, è proprio quello di risolvere la problematica ora esposta grazie ad un apposito oggetto che incapsuli in un unico container i dati necessari alle elaborazioni del sistema!

Riprendiamo l'esempio del gruppo aziendale.

Supponiamo di avere un infrastruttura che permetta di registrare le movimentazioni di magazzino delle differenti società, per mezzo di un sistema di messaggi. Possiamo ipotizzare l'esistenza di un Servizio per ogni società che, tra i diversi metodi, presenta il seguente:

  1. ... 
  2. processStoreChange(StoreMsg msg){ ... } 
  3. ... 

Il processamento dei messaggi presuppone che tutte le informazioni indispensabili siano espresse nel messaggio o localmente al Servizio; inoltre il meccanismo di notifica sembrerebbe essere una semplice azione di dispatching dei messaggi ... Cosa accadrebbe se l'elemento di notifica dovesse passare al Servizio altre informazioni non inizialmente previste o, semplicemente, esterne al Servizio stesso? Inizialmente si può pensare ad una ristrutturazione del metodo ... Supponiamo che il processamento di un messaggio richieda anche l'aggiornamento di un sistema di riordino e che sia necessario registrare l'attività svolta; possiamo ipotizzare una nuova signature del metodo nella forma:

  1. ... 
  2. processStoreChange(StoreMsg msg, LocalOrdersManager ordMan, UnitLogger log){ ... } 
  3. ... 

È evidente che iterando in questo approccio, oltre alle possibili ripercussioni a cascata dovute alla modifica dell'interfaccia del Servizio, si può giungere ad una signature degenere del metodo. Per evitare questa problematica, ma rimanere aperti alle future esigenze, si può sviluppare un oggetto Context che incapsulerà tutte le informazioni necessarie alla elaborazione!

  1. class StoreProcessContext 
  2.   UnitLogger log; 
  3.   LocalOrdersManager ordMan; 
  4.   StoreMsg actualMsg; 
  5.    
  6.   StoreProcessContext(StoreMsg msg, String nationId) 
  7.     actualMsg = msg; 
  8.     ordMan    = new LocalOrdersManager(nationId); 
  9.     log       = LogFactory.getLoggerFor(nationId); 
  10.    
  11.   UnitLogger getLogger() 
  12.     Return log;  
  13.    
  14.   void updateOrdersManager(int inQuantity, int outQuantity, Date date) 
  15.   {  
  16.     ordMan.newChangeDate(date); 
  17.     ordMan.regIn(inQuantity); 
  18.     ordMan.regOut(outQuantity); 
  19.     ordMan.colculateLevel(); 
  20.  
  21.   StoreMsg getActualMessage() 
  22.     return actualMsg; 
  23.  
  24.   ... 

A questo punto la signature del nostro metodo diverrà più snella e robusta:

  1. ... 
  2. processStoreChange(StoreProcessContext ctx){ ... } 
  3. ... 

L'impatto dovuto alla necessità di utilizzare nuove informazioni diventa, con questo approccio, limitato all'oggetto Context che può essere alterato con minore sforzo rispetto alla situazione di partenza. Si può pensare di rendere la classe particolarmente generica e flessibile ma è sempre bene riflettere sui rischi relativi ad una tale opzione così come su quelli di un eccessivo sovraccarico di responsabilità!

Role Object Pattern

Nelle applicazioni di medie e grandi dimensioni è facile scontrarsi con un problema di modellazione: lo stesso elemento reale gioca ruoli differenti in base al contesto in cui è posto.

L'idea di ruolo può essere sviluppata partendo dal concetto d'interfaccia ed usare l'ereditarietà multipla per comporre ruoli differenti: questo approccio può portare ad un contratto ridondante e di difficile gestione!

Obiettivo del pattern in discussione è proprio quello offrire una soluzione alla problematica dei ruoli grazie ad un insieme di role object che vengono dinamicamente aggiunti o rimossi rispetto ad un core object.

Riprendiamo l'esempio del gruppo aziendale.

Supponiamo di avere la figura del Direttore di Magazzino: questi è un dipendente di una data società ma in quanto direttore ha dei sottoposti; lo stesso personaggio ha il ruolo dell'acquirente dato che firma gli ordini di approvvigionamento del magazzino verso i fornitori esterni! Come modellare questa situazione? Come permettere ulteriori evoluzioni della figura citata?

Partiamo col definire una interfaccia, potremmo semplicisticamente dire il ruolo di base:

  1. interface Employee{ ... } 

Questa interfaccia ospiterà i metodi fondamentali del ruolo IMPIEGATO, come quelli necessari per la gestione delle generalità, e quelli necessari alla gestione degli altri ruoli che l'impiegato può assumere:

  1. interface Employee 
  2. {  
  3.   String getName(); 
  4.   long getId(); 
  5.   boolean hasRole(String role); 
  6.   EmployeeRole getRole(String role); 
  7.   void removeRole(String role); 
  8.   void addRole(String role); 

A questo punto realizzeremo il protocollo dell'interfaccia nell'oggetto EmployeeCore:

  1. class EmployeeCore implements Employee{ ... } 

In modo analogo definiamo la classe EmployeeRole da cui deriveranno gli altri possibili ruoli attribuibili al dato impiegato:

  1. class EmployeeRole implements Employee{ ... } 
  2.  
  3. class Director extends EmployeeRole{ ... //metodi specifici del Direttore ... } 
  4.  
  5. class Buyer extends  EmployeeRole{ ... //metodi specifici dell'acquirente ... } 

È necessario precisare che nella classe EmployeeRole verrà ospitato un riferimento ad una data istanza della classe EmployeeCore a cui verrano delegate tutte le funzionalità di base come previste nell'interfaccia Employee:

  1. class EmployeeRole implements Employee 
  2.   ... 
  3.    
  4.   String getName() 
  5.     return core.getName(); 
  6.    
  7.   ... 
  8.    
  9.   boolean hasRole(String role) 
  10.     return core.hasRole(); 
  11.    
  12.   ... 

Possiamo ora immaginare di facilitare la gestione della creazione delle varie parti, loro collegamento e manipolazione degli oggetti grazie ad un'apposita classe:

  1. class EmpsManager 
  2.   ... 
  3.  
  4.   Employee getEmployee(String key){ ... } 
  5.  
  6.   ...   

A questo punto è possible utilizzare gli elementi sviluppati in modo semplice:

  1. Employee emp = manager.getEmployee("JOHN");  
  2.  
  3. if (emp.hasRole("director")) { 
  4.   Director director = (Director)emp; 
  5.   ... 
  6.  
  7. if (emp.hasRole("buyer")) { 
  8.   Buyer buyer = (Buyer)emp; 
  9.   ... 

Supponiamo ora che nel gruppo aziendale esista un Direttore Generale a cui gli altri Direttori di Unità devono far riferimento: in questa visione il nostro direttore è un subordinato. Per supportare il nuovo ruolo provvederemo come segue:

  1. ... 
  2.  
  3. class Subordinate extends EmployeeRole 
  4.   // metodi propri del ruolo 
  5.   ... 
  6.  
  7. ... 
  8.  
  9. emp.addRole("subordinate"); 
  10.  
  11. ... 
  12.  
  13. if (emp.hasRole("subordinate")) { 
  14.   Subordinate subordinate = (Subordinate)emp; 
  15.   ... 
  16.  
  17. ... 

Conclusioni

Le soluzioni di design presentate in questa sede non sono prive di trade-off a cui prestare attenzione: basti pensare alla complessità introdotta, alla necessità di gestione e sincronizzazione di strutture articolate o ai rischi di violazione dei principi di base del design ad oggetti. Ciò nonostante le idee illustrate possono risultare utili e ponendo un po' di attenzione le potete trovare applicate in molti framework e software moderni!

Sono sicuro che durante la lettura di questo articolo alcuni di voi si saranno domandati se era veramente necessario ricorrere a certe soluzioni di design, quando alcuni problemi potevano risolversi in altro modo o persino evitati ... Potreste aver ragione, ma ve la sentite di scartare a priori altri tool che vi potrebbero servire a recuperare i disastri fatti da altri????

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 / Design Patterns.

Discuti sul forum   Stampa

Cosa ne pensi di questo articolo?

Discussioni

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