Nell'ultimo progetto al quale ho collaborato mi è capitato di dover lavorare con delle liste che, essendo periodicamente aggiornate da un database esterno, rimanevano inalterate per un periodo di tempo relativamente elevato.
La prima considerazione che mi venne in mente, stendendo l'analisi del progetto, fu "questo è il caso in cui il pattern Singleton calza a pennello".
Il pattern Singleton è uno dei più conosciuti tra i patterns dell'ingegneria del software.
Una classe Singleton è essenzialmente una classe che permette la creazione di una e una sola istanza di se stessa.
Pertanto, postulando che la tabella dei prodotti sul database rimanga inalterata, potremmo 'caricare' tutti i prodotti in una collezione tipizzata che potrebbe soddisfare tutte le richieste senza generare altri accessi nel database.
Se dunque partiamo dal nostro progetto (è possibile scaricare sempre liberamente la versione aggironata) dovremmo riscrivere solo la classe ProdottiFacede, creando una nuova classe che chiameremo ProdottiFacadeSingleton.
Premettendo che ci sono diverse scuole di pensiero sulla creazione di classi Singleton, io vi rimando a due articoli MSDN: "Implementig Singleton in C#" ed "Exploring the Singleton Design Pattern". Ad ogni modo la nostra classe Singleton risulterà, almeno in questo articolo, molto semplice in quanto espone dei metodi che restituiscono dei risultati; infatti, non esponendo proprietà che potrebbero mutare, non è necessario tener conto della concorrenza.
Comunque a mio avviso l'implementazione di una classe singleton cambia a seconda delle esigenze e dell'obbiettivo che ci si pone di ottenere dall'implementazione di questo pattern.
Creiamo innanzitutto la nostra classe statica e il nostro membro privato che sarà poi la nostra lista di prodotti. Il costruttore statico della classe si preoccuperà di andare a invocare l'oggetto di accesso ai dati per popolare la nostra lista privata.
[code language="C#"]
public static class ProdottiFacadeSingleton
{
private static List<Prodotto> _listaProdotti = null;
static ProdottiFacadeSingleton()
{
ProdottiDao prodottiDao = new ProdottiDao();
_listaProdotti = prodottiDao.GetProdotti();
}
}
[/code]
Fatto ciò dovremmo preoccuparci di creare i metodi per la restituzione dei dati e il conteggio del numero totale dei record.
Creamo quindi due metodi statici pubblici che chiameremo con lo stesso nome dei metodi della classe ProdottiFacade e che accetteranno gli stessi valori. In questo modo per passare dall'invocare il metodo statico o meno all'interno dell'UI, ci basterà modificare il TypeName dell'Objcet Data Source.
Ci rimane solo da risolvere il problema dell'ordinamento e della ricerca, non dimenticate però che stiamo usando i generics, la più importante 'feature' del .Net Framework 2.0!
(Permettetemi una divagazione: ogni volta che parlo di generics mi viene in mente l'introduzione di Francesco Balena al libro Programming Visual C# 2005: The Base Class Library introducendo i generics: "Unless you are absolutely new to Microsoft .Net programming-or you're a .Net developer who has lived on a desert island for the last two years-you should have heard about generics and the fact that they are the most important addition to Microsoft Visual C# and other .NET languages.")
Il metodo GetProdottiPaginati si dovrà occupare di filtrare la lista dei prodotti secondo le richieste dell'UI e successivamente ordinarla. Ritengo logico far compiere al metodo le due azioni in questa successione, in tal modo infatti l'ordinamento avverrà solo e soltanto per quella porzione della lista che soddisfa i criteri di ricerca.
Filtro e ordinamento saranno demadati a due metodi privati:
[code language="C#"]
private static void filtra(ParametriProdotti param, ref List<Prodotto> prodotti)
{
if (!param.idProdotto.Equals(0))
{
prodotti = prodotti.FindAll(new Predicate<Prodotto>(
delegate(Prodotto p)
{
return p.idProdotto.Equals(param.idProdotto);
})
);
}
if(!string.IsNullOrEmpty(param.nomeProdotto))
{
prodotti = prodotti.FindAll(new Predicate<Prodotto>(
delegate(Prodotto p)
{
return p.nomeProdotto.Contains(param.nomeProdotto);
})
);
}
if (!string.IsNullOrEmpty(param.descProdotto))
{
prodotti = prodotti.FindAll(new Predicate<Prodotto>(
delegate(Prodotto p)
{
return p.descProdotto.Contains(param.descProdotto);
})
);
}
if (!string.IsNullOrEmpty(param.modelloProdotto))
{
prodotti = prodotti.FindAll(new Predicate<Prodotto>(
delegate(Prodotto p)
{
return p.modelloProdotto.Contains(param.modelloProdotto);
})
);
}
}
[/code]
[code language="C#"]
private static void ordina(string sortColumn, ref List<Prodotto> prodotti)
{
if (!string.IsNullOrEmpty(sortColumn))
{
prodotti.Sort(new Comparison<Prodotto>(delegate(Prodotto p1, Prodotto p2)
{
try
{
Type type1 = p1.GetType();
PropertyInfo proprieta1 = type1.GetProperty(sortColumn);
Type type2 = p2.GetType();
PropertyInfo proprieta2 = type2.GetProperty(sortColumn);
object[] index = null;
object Obj1 = proprieta1.GetValue(p1, index);
object Obj2 = proprieta2.GetValue(p2, index);
IComparable Ic1 = (IComparable)Obj1;
IComparable Ic2 = (IComparable)Obj2;
return sortParam * Ic1.CompareTo(Ic2);
}
catch
{
throw new ArgumentException("La comparazione non è possibile");
}
}));
}
}
[/code]
Come avrete notato per i due metodi privati ho usato due approcci diversi: nel primo ho separato intenzionalmente i criteri di ricerca prendendo in considerazione separatamente ogni proprietà della classe Prodotti; nel secondo metodo, l'ordinamento, ho cercato, tramite reflection, di verificare quale fosse la proprietà per la quale ordinare senza andare a verificarle singolarmente.
Ho deciso di usare approcci diversi a fronte di due distinti problemi: l'ordinamento non cambierà mai, potrà sempre e solo essere ascendente o discendente; la ricerca nella determinata proprietà, au contraire, potrebbe cambiare: l'utente infatti potrebbe richiedere, nella descrizione, che la ricerca restituisse tutti i prodotti contenenti quella data espressione inserita, mentre per il nome che vengano restituiti solo quelli che iniziano con i valori inseriti. Con questo approccio il codice risulta altamente manutenibile.
Paginazione Lato Server Parte 1 - Oggetti che supportano la paginazione
Paginazione Lato Server Parte 2 - Ricerca negli oggetti che supportano la paginazione
Paginazione Lato Server Parte 3 - Oggetti che non supportano la paginazione con ricerca