Fat Client e DB: un classico ... o un incubo?
La storia si ripete. Sempre. Ognuno di noi ha visto le proprie applicazioni database degradare le proprie performance proporzionalmente all'aumentare della complessità della struttura dei dati e della quantità di forms appartenenti al progetto.
Se poi la struttura del db è disegnata "non proprio bene" o non è perfettamente consona alla quantità e tipologia dei dati che ospita, ecco il "coup de grace". La nostra applicazione soccombe miseramente, dando all'utilizzatore quella spiacevole sensazione di lentezza e scarsa reattività che ci si augura sempre di non provare quando si utilizza un applicativo desktop.
Stiamo parlando di applicazioni win32 client/server realizzate con Delphi, che accedono ai dati via ODBC ad un qualsiasi database relazionale SQL based.
Pensate ad un gestionale: in media è costituito da 50-100 tabelle (database), che necessitano di gestione completa (possibilità di visualizzare, ricercare, inserire modificare e rimuovere dati). Ci si trova pertanto ad avere da cinquanta a cento forms, solo per la gestione dei dati.
Gran parte delle form contiene controlli visuali collegati al database (i cosiddetti controlli "DBAware"): il binding verso le tabelle ed i campi che contengono i dati da gestire sono stati definiti a design time: ciò implica che al verificarsi dell'evento OnDataChange, ogni componente DBAware cerca di recuperare dal database il dato che deve visualizzare.
Con questo scenario, se le form venissero create allo startup dell'applicazione attenderemmo minuti prima di vedere comparire la main form, oltre a causare un consumo smodato della memoria di sistema.
Lazy? No: On Demand!
La soluzione c'è, ovviamente: semplice, efficiente e definitiva.
Dobbiamo evitare che la nostra applicazione crei automaticamente le form allo startup, e crearle noi programmaticamente, solo quando e se ci servono.
Selezioniamo il menu [Project->Options] e nel tab Forms togliamo dal pannello "Auto create forms" tutte le forms presenti (eccetto la main form...).
E per creare la form che ci serve operiamo come segue:
Con la semplice esplorazione della collezione Forms contenuta nella variabile globale Screen() cerchiamo (per nome) se c'è già la form che dobbiamo aprire e se non la troviamo, la costruiamo, come indicato nell'esempio
for i := 0 to Pred(Screen.FormCount) do begin
if uppercase(Screen.Forms[i].Name) = "FormNameToSearchFor" then
begin
found := TForm(screen.Forms[i]);
break;
end;
end;
if not assigned(found) then
begin
//codice per la creazione della form
end;
end;
In alternativa è anche possibile (e questa è la soluzione che normalmente adotto nelle mie applicazioni), creare a distruggere la form ogni volta che la si deve usare. Lo si può fare automaticamente usando l'evento OnClose della form, con la seguente istruzione:
procedure TForm1.FormClose(Sender: TObject);
begin
self.release;
end;
Se adotterete questa soluzione non sarà necessario esplorare la variabile globale Screen in quanto la form sarà sempre da costruire.È chiaro che questa metodologia ci costringe ad avere un dispatcher che, dato il nome di una form ci restituisce un'istanza di quella form, ma vi garantisco che i vantaggi sono innumerevoli.
Reflective Factory in Delphi
Il programmatore attento si pone subito la domanda corretta: come possiamo ottenere a runtime l'istanza di una form che abbiamo progettato e costruito a design time e di cui conosciamo solo il nome? Normalmente Delphi ci "regala" il codice per avere le istanze di tali form. Una breve sbirciatina al file di progetto <mioProgetto>.dpr ci rivela come ciò avviene:
Application.CreateForm(TForm1, Form1);
A noi ciò non va bene in quanto i due parametri da passare al metodo CreateForm sono:
il primo di tipo TFormClass, ed il secondo un untyped param che serve per contenere il reference della classe creata.
Un altro modo per creare una form è quello di "costruirla" invocando il suo costruttore Create(AOwner: TComponent), ma anche in questo caso il nome della form non è sufficiente in quanto non possiamo certo creare un'istanza di un oggetto a partire da una stringa scrivendo del codice tipo:
'TForm1'.create(Application);
Peggio ancora un pessimo e poco efficiente codice come il seguente
if FormNameToSearchFor = "ArticoliForm" then
begin
Application.CreateForm(TArticoliFrm, ArticoliFrm);
end;
if FormNameToSearchFor = "OrdiniForm" then
begin
Application.CreateForm(TOrdiniFrm, OrdiniFrm);
end;
//.... eccetera!!!!!
Ci serve quindi il modo per cercare il tipo di una classe dal suo nome. Come sempre l'ottima documentazione di Delphi ci viene in aiuto. La funzione FindClass è quello che ci serve:
function CreateObject(FormNameToSearch: string): TObject;
var
clazz: TFormClass;
begin
result := nil;
clazz := TFormClass(FindClass(FormNameToSearch));
if assigned(clazz) then
begin
//trovato, creo la classe
result := clazz.Create(Application);
end
else begin
//nome non trovato
....
end;
end;
Il cast a TFormClass è necessario, in quanto FindClass ritorna un generico TPersistentClass.
Questo codice funziona solo se le form che cerchiamo sono state preventivamente registrate con la funzione
RegisterClass(AClass: TPersistentClass).
che permette allo streaming system di Delphi di "essere cosciente" dell'esistenza della form (non dimenticate che abbiamo eliminato la creazione automatica delle forms).
Finalmente abbiamo ciò che ci serve. L'istanza a runtime di una specifica form che abbiamo disegnato con l'IDE.
È evidente che quanto detto è solo uno spunto. Ma ora abbiamo tutti gli strumenti per poter generalizzare, centralizzare e lasciare che il numero di form cresca a dismisura, visto che durante l'esecuzione del nostro applicativo le form presenti in memoria saranno solo quelle effettivamente in uso. Il consiglio che vi do è quello di fare in modo che tutte le form della vostra applicazione discendano dallo stesso padre, che è una VOSTRA classe:
Figura 1
In questo modo potrete centralizzare tutte le operazioni comuni nella classe padre, come per esempio l'aspetto ed i colori o addirittura la manipolazione e il salvataggio dei dati.
Ma questa è un'altra storia ...