MokaByte 52 - Maggio 2001
Foto dell'autore non disponibile
di
Gianluca Morello
Corso CORBA
IV parte: Il pattern Factory in 
uno scenario CORBA
Uno dei compiti più complessi nell’ambito della programmazione distribuita è l’implementazione e la gestione della concorrenza.

Un oggetto remoto distribuito sulla rete può essere utilizzato contemporaneamente da più client. Questo è uno dei fattori che rendono allettante la programmazione distribuita poiché l’utilizzo in concorrenza di una qualunque risorsa implica un suo migliore sfruttamento.

La gestione della concorrenza in ambiente distribuito è strettamente legata al design dell’applicazione e tipicamente va a ricadere  in uno dei tre approcci:

  • Unico thread: il thread è unico, le richieste sono gestite in modo sequenziale ed eventualmente accodate.
  • Un thread per client: viene associato un thread alla connessione con il client; le successive richieste del client sono a carico di questo thread.
  • Un thread per request: esiste un pool di thread utilizzati in modo concorrente per rispondere alle richieste dei client


Esisteranno comunque situazioni in cui i differenti thread dovranno accedere ad una risorsa condivisa (Es. connessione a DB, file di log, …); in questi casi l’accesso alla risorsa andrà opportunamente sincronizzato.

Come si vedrà nei prossimi articoli, con CORBA è possibile specificare le politiche di gestione dei thread mediante l’utilizzo degli object adapter. Nella pratica questi aspetti vengono spesso gestiti applicativamente con l’adozione di opportuni pattern di programmazione.

Questo permette di realizzare soluzioni complesse del tutto indipendenti dall’implementazione e dalla tecnologia. Tipicamente queste soluzioni sono attuate con l’adozione del pattern factory.
 
 
 

Il pattern Factory
Quando si istanzia un oggetto è necessario fare un riferimento diretto ed esplicito ad una precisa classe, fornendo gli eventuali parametri richiesti dal costruttore. Questo vincola implicitamente l’oggetto utilizzatore all’oggetto utilizzato. Se questo può essere accettabile nella maggior parte dei casi, talvolta è un limite troppo forte.


Figura 1 — Pattern Factory

In questi casi può essere molto utile incapsulare in una classe specializzata tutte le valutazioni relative alla creazione dell’oggetto che si intende utilizzare. Questa soluzione è il più noto tra i creational patterns: il pattern Factory (anche detto Factory Method o Virtual Constructor).
Factory letteralmente vuol dire Fabbrica ed è proprio questo il senso dell’oggetto Factory: fabbricare sulla base di alcune valutazioni un determinato oggetto. Più formalmente, facendo riferimento anche alla figura, un oggetto Factory fabbrica oggetti ConcreteProduct appartenenti ad una determinata famiglia specificata dalla sua interfaccia (o classe astratta) Product. 
Un client non crea mai in modo diretto un’istanza del Product (in un contesto remoto probabilmente non ne conoscerà nemmeno la classe), ma ne ottiene un’istanza valida attraverso l’invocazione del FactoyMethod sull’oggetto Factory.
In questo modo è possibile sostituire in ogni momento l’implementazione ConcreteProductA con un’implementazione omologa ConcreteProductB senza che un eventuale client se ne accorga.
In realtà i vantaggi elencati in precedenza sono già impliciti in uno scenario CORBA (il disaccoppiamento è garantito dalla funzionalità Proxy dello stub). Nella programmazione distribuita il pattern Factory ha però altri vantaggi, in particolare consente di implementare meccanismi di load-balancing e fault-tolerance.

Poiché è la Factory a determinare la creazione del Product, essa potrà:

  • istanziare un oggetto per ogni client
  • applicare un round-robin tra le differenti istanze già create, ottenendo un semplice load-balancing
  • restituire differenti tipologie di oggetti appartenenti alla famiglia Product sulla base di valutazioni legate all’identità del client
  • implementare un semplice fault-tolerance, escludendo dal pool di oggetti quelli non più funzionanti o non più raggiungibili via rete.

Un esempio di Factory
Per sperimentare un tipico caso di applicazione del pattern Factory realizziamo il classico “carrello della spesa”. Per l’esempio saranno implementate la classe carrello (ShoppingCart) ed una sua Factory (ShoppingCartFactory). La prima fornirà una funzionalità di acquisto ed una di restituzione del contenuto, la seconda sarà dotata del solo metodo getShoppingCart. 

Definiamo l’IDL

// IDL
module shopping {
 
 struct Book {  
  string Author;
  string Title;
 };
 
 typedef sequence <Book> BookList;
 
 interface ShoppingCart {
  void addBook(in Book book);
  BookList getBookList(); 
 };
 
 interface ShoppingCartFactory {
  ShoppingCart getShoppingCart(in string userID);
 }; 
};

Sono definite come interface sia la Factory (con il Factory Method getShoppingCart), sia il carrello vero e proprio. La Factory può essere considerata una classe di infrastruttura, mentre tutti i metodi di business sono implementati nello ShoppingCart.

La classe ShoppingCartImpl implementa i due semplici metodi di business e non presenta nulla di nuovo rispetto a quanto visto nei precedenti articoli

package server;

import shopping.*;
import java.util.Vector;

public class ShoppingCartImpl extends _ShoppingCartImplBase {

   Vector v = new Vector();
  
   public ShoppingCartImpl() {
    super();
   }

 // Aggiunge un libro al carrello
 public void addBook(Book book) {
  v.add(book); 
 }
 
 // Restituisce l'elenco dei libri acquistati
 public Book[] getBookList() {
  Book[] books = new Book[v.size()];
  
  for (int i=0; i<v.size(); i++)
   books[i] = (Book) v.elementAt(i);
  
  return books;
 } 
}

Più interessante è l’oggetto Factory che ha il compito di generare le istanze di ShoppingCartImpl da assegnare ai client. Nel metodo getShoppingCart viene stabilita la politica di creazione e restituzione delle istanze di carrello; nel caso in esame la decisione è ovvia in quanto il carrello ha evidentemente un rapporto uno a uno con i client e ad ogni client verrà quindi assegnata una nuova istanza di ShoppingCart.

Per memorizzare le varie istanze viene utilizzato un oggetto di tipo Dictionary. Alla prima connessione  dell’utente  la Factory creerà il carrello e lo “registrerà” sull’ORB. La Factory può ottenere un riferimento valido all’ORB invocando il metodo _orb() fornito da org.omg.CORBA.Object.

Ecco la classe Factory

package server;

import shopping.*;
import java.util.*;

public class ShoppingCartFactoryImpl extends 
       _ShoppingCartFactoryImplBase {

 private Dictionary allCarts = new Hashtable();
 
   public ShoppingCartFactoryImpl() {

    super();
 }

 public synchronized ShoppingCart 
      getShoppingCart(String userID) {

     // Cerca il carrello assegnato allo userID...
     shopping.ShoppingCart cart = 
    (shopping.ShoppingCart) allCarts.get(userID);

     // ...se non lo trova...
     if(cart == null) {
      
        // Crea un nuovo carrello...
        cart = new ShoppingCartImpl();

   // ...e lo attiva sull'ORB
   _orb().connect(cart);
    
   System.out.println("Created " + userID 
      + "'s cart: " + cart);
      
        // Salva nel dictionary associandolo allo userID
        allCarts.put(userID, cart);
     }

     // Restituisce il carrello
     return cart;
 }
}

E’ da notare che la Factory sarà utilizzata in concorrenza da più client, di conseguenza sarà opportuno sincronizzare il metodo getShoppingCart per ottenere un’esecuzione consistente.

Per quanto detto in precedenza, un client otterrà un oggetto remoto di tipo ShoppingCart interagendo con la Factory. Pertanto, l’unico oggetto registrato sul Naming Service sarà l’oggetto Factory. La registrazione sarà effettuata dalla classe server con il nome “ShoppingCartFactory” con le stesse modalità viste negli scorsi articoli (il codice qui non viene mostrato).

Dopo aver ottenuto il reference allo ShoppingCart assegnato, il client potrà operare direttamente su questo senza interagire ulteriormente con la Factory

package client;

import shopping.*;
import java.io.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;

public class ShoppingCartClient {
  
 public static void main(String args[]) {
   
  if (args.length != 3) {
      System.err.println("Uso corretto: 
    java ShoppingCartClient userId Autore Titolo");
        return;
     }

  try{
     
      // Crea ed inizializza l'ORB
     ORB orb = ORB.init(args, null);

        // Root naming context
        org.omg.CORBA.Object objRef = 
    orb.resolve_initial_references("NameService");
        
   NamingContext ncRef = 
    NamingContextHelper.narrow(objRef);
 
        // Utilizzo il Naming per ottenere il 
   // riferimento all'oggetto Factory
        NameComponent nc = 
    new NameComponent("ShoppingCartFactory", "");
        NameComponent path[] = {nc};
        ShoppingCartFactory factory = 
     ShoppingCartFactoryHelper.narrow
          (ncRef.resolve(path));

        // Ottengo dalla Factory un'oggetto ShoppingCart
        ShoppingCart cart =
        factory.getShoppingCart(args[0]);
      
        // Aggiungo un libro
        cart.addBook(new Book(args[1],args[2]));
      
        // Ottengo la lista dei libri e la stampo
        Book[] list = cart.getBookList();      

        for(int i=0; i<list.length; i++)
         System.out.println("Autore " + 
     list[i].Author + " - Titolo " 
        + list[i].Title);
            
    } catch (Exception e) {
     e.printStackTrace();
    }
 }
}

I passi necessari per l’esecuzione sono i soliti (si veda [1]). Al client vanno “passati” lo userId, l’autore ed il titolo del libro da aggiungere al carrello.

L’adozione di un pattern è una scelta del tutto indipendente dalla tecnologia e quindi adattabile a qualunque altro scenario (ad esempio RMI). Un design come quello visto rende l’architettura più robusta ed adattabile, consentendo di modificare e configurare il comportamento anche a start-up avvenuto senza alcun impatto sui client. In ottica Enterprise la resistenza ai cambiamenti è di massimo interesse.
 
 
 

Conclusioni
In questo articolo abbiamo presentato il pattern Factory ed una semplice soluzione CORBA che ne fa uso. Come si è già accennato, le politiche relative alla gestione delle istanze sono configurabili anche utilizzando gli Object Adapter. Il mese prossimo inizieremo a studiare il più semplice tra questi: il Basic Object Adapter.
 
 
 

Bibliografia
[1] G. Morello - "Corso CORBA IV Parte: CORBA Naming Service", Mokabyte N. 51, Aprile 2001
[2] G. Morello - "Corso CORBA III Parte: un semplice esempio", Mokabyte N. 50, Marzo 2001
 

Vai alla Home Page di MokaByte
Vai alla prima pagina di questo mese


MokaByte®  è un marchio registrato da MokaByte s.r.l.
Java®, Jini®  e tutti i nomi derivati sono marchi registrati da Sun Microsystems; tutti i diritti riservati
E' vietata la riproduzione anche parziale
Per comunicazioni inviare una mail a
mokainfo@mokabyte.it