In questa seconda parte (qui la prima) completerò la parte applicativa introducendo lo strato di factory e cercherò di fare un'analisi, spero oggettiva, del laro svolto dei sui limiti e presenterò alcune soluzioni a questi limiti.
Factory Layer
Nella mia ottica il factory si occupa solo ed esclusivamente della generazione delle istanze delle classi di business, il factory conosce molto bene lo strato di business. Partiamo, come sempre, dal codice che non differisce molto da quello presentato nel precedente articolo riguardo allo strato di persistenza:

//
// ASSEMBLY
// Topics.Common.Factory.dll
//

public sealed class FactoryEngine
{
    
private FactoryEngine()
    {
    
    }

    
private static FactoryEngine _engine;
    
    
/*
        Singleton
    */
    
public static FactoryEngine Engine
    {
        
get
        
{
            
if( _engine == null )
            {
                _engine = 
new FactoryEngine();
            }
            
            
return _engine;
        }
    }
    
    
private MyCustomerFactory _myCustomers
    
public MyCustomerFactory MyCustomers
    {
        
get
        
{
            
ifthis._myCustomers == null )
            {
                
this._myCustomers = MyCustomerFactory.CreateInstance();
            }
            
            
return this._myCustomers;
        }
    }
}

public abstract class MyCustomerFactory
{
    
public static MyCustomerFactory CreateInstance()
    {
        
/*
            OMISSIS
            Legge da file .config quale tipo di
            Factory debba istanziare e da quale
            assembly, tramite Activator.CreateInstance( ... )
            inizializza la classe "reale" corretta
        */
    
}
    
    
/*
        MyCustomerCollection (omissis) è una semplice 
        collection fortemente tipizzata di MyCustomers
    */
    
public abstract MyCustomerCollection FindCustomersByCompanyName( string companyName );
    
public abstract MyCustomer FindByGuid( Guid pk );
}

//
// ASSEMBLY
// Topics.MyApplication.SqlFactory.dll
//

public class MyCustomerSqlFactory : MyCustomerFactory
{
    
public override MyCustomerCollection FindCustomersByCompanyName( string companyName )
    {
        
//Si connette a SQL
        
        //Invoca la SP del caso
        
        //Ottiene un SqlDataReader (IDataReader)
        
        //Crea la collection passando al costruttore il DataReader
    
}
    
    
public override MyCustomer FindByGuid( Guid pk )
    {
        
//Si connette a SQL
        
        //Invoca la SP del caso
        
        //Ottiene un SqlDataReader (IDataReader)
        
        //Crea l'istanza della classe passando al costruttore il DataReader
    
}
}
Anche qui vale la stessa regola esposta per lo strato di persistenza, possiamo sostituire il tipo factory a caldo senza che l'applicazione debba essere ricompilata, un semplicistico esempio potrebbe essere:
public class MyCustomerRemotingFactory : MyCustomerFactory
{
    
public override MyCustomerCollection FindCustomersByCompanyName( string companyName )
    {
        
/*
            Si connette a Server remoto, dove probabilmente
            sta girando un servizio che espone un interfaccia
            per recuperare gli oggetti necessari
        */
    
}
    
    
public override MyCustomer FindByGuid( Guid pk )
    {
        
//...
    
}
}
La parte di codice si conclude qui (forse), da quello che abbiamo visto sino ad ora possiamo cominciare a trarre una prima considerazione, la struttura che abbiamo realizzato rispetta un sanissimo principio della strutturazione a layer di un'applicazione:
Ogni Layer conosce il Layer direttamente sotto di lui ma non conosce in nessun modo il Layer che lo consuma.
Un primo "limite" che è doveroso sottolineare è che per far si che il factory/persistance stiano in assembly diversi da quello del domain, al fine di garantire la sostituzione a caldo, i costruttori delle classi di business devono essere pubblici, cosa che personalmente non mi paice molto, ma tant'è...
Evolvendo la struttura della classe di business presentata, arricchendola quindi con qualche informazione in più, salta subito all'occhio un problema non indifferente:
public class MyCustomer : MyObject
{
    
//...
    
    
private MyOrderCollection _orders;
    
public MyOrderCollection Orders
    {
        
get
        
{
            
//LazyLoad
            
ifthis._orders == null )
            {
                
this._orders = ....
            }
            
            
return this._orders; 
        }
    }
    
    
//...
}
Oggiungiamo alla ns classe MyCustomer una proprietà pubblica che rappresenta gli ordini fatti da quel particolare cliente, salta subito all'occhio che ci troviamo di fronte a 2 grossi problemi:
  • Se MyCustomer carica direttamente gli ordini senza passare da un factory vanifichiamo tutto il lavoro fatto per separare i Layer, perchè il cambiamento di Factory ci imporrebbe di riscrivere questa parte di codice per adeguarla al nuovo factory;
  • Se diamo a MyCustomer la possibilità di usare il factory contraveniamo innanzitutto al principio esposto in precedenza e soprattutto creiamo un riferimento circolare;
La soluzione è venuta (come al solito) da internet, sul blog di Stefano Mostarda, che ha affrontato una problematica pressochè identica.
Seguendo la sua scelta ho reimplementato la proprietà Orders nel seguente modo, trasformandola però in un metodo:

public class MyCustomer : MyObject
{
    
//...
    
    
private MyOrderCollection _orders;
    
public event OrdersLazyLoadEventHandler OrdersLazyLoad = null;
    
private void OnOrdersLazyLoad( OrdersLazyLoadEventArgs e )
    {
        
if( OrdersLazyLoad != null )
        {
            OrdersLazyLoad( 
this, e );
        }
    }
    
    
public MyOrderCollection GetOrders()
    {
        
get
        
{
            
//LazyLoad
            
ifthis._orders == null )
            {
                OrdersLazyLoadEventArgs args = 
new OrdersLazyLoadEventArgs();
                
this.OnOrdersLazyLoad( args );
                
this._orders = args.Result;
            }
            
            
return this._orders; 
        }
    }
    
    
//...
}

public delegate void OrdersLazyLoadEventHandler( object sender, OrdersLazyLoadEventArgs e );

public class OrdersLazyLoadEventArgs : EventArgs
{
    
public OrdersLazyLoadEventArgs()
    {
    
    }
    
    
private MyOrderCollection _result;
    
public MyOrderCollection Result
    {
        
getreturn this._result; }
        
setthis._result = value; }
    }
}
A questo punto l'unica accortezza è che il factory sottoscriva l'evento per sapere quando qualcuno cerca di accdere alla collection "Orders", quindi il factory diventa una cosa del tipo:
public class MyCustomerSqlFactory : MyCustomerFactory
{
    
void Wire( MyCustomer item )
    {
        item.OrdersLazyLoad += 
new OrdersLazyLoadEventHandler( Customer_OrdersLazyLoad );
    }
    
    
void Unwire( MyCustomer item )
    {
        item.OrdersLazyLoad -= 
new OrdersLazyLoadEventHandler( Customer_OrdersLazyLoad );
    }
    
    
void Customer_OrdersLazyLoad( object sender, OrdersLazyLoadEventArgs e )
    {
        
//Chiama il factory degli ordini per ottenere quelli del sender...
        
e.Result = ...
    }

    
public override MyCustomerCollection FindCustomersByCompanyName( string companyName )
    {
        
//Si connette a SQL
        
        //Invoca la SP del caso
        
        //Ottiene un SqlDataReader (IDataReader)
        
        //Crea la collection passando al costruttore il DataReader
        
        /*
            Lo stesso meccanismo è gestito anche dalla collection
            che aggancia l'evento per ognuno degli item e a sua volta
            lo "rincula" all'eterno
        */
        
this.Wire( /* loadedInstance */ );
    }
    
    
public override MyCustomer FindByGuid( Guid pk )
    {
        
//Si connette a SQL
        
        //Invoca la SP del caso
        
        //Ottiene un SqlDataReader (IDataReader)
        
        //Crea l'istanza della classe passando al costruttore il DataReader
        
        
this.Wire( /* loadedInstance */ );
    }
}


Questo approccio apre lo scenario a svariate possibilità tra cui ad esempio il caricamento asincrono dei dati cosa che con una proprietà sarebbe impossibile, o la possibilità di forzare il reload dei dati senza inquinare la classe di business con strani metodi del tipo "InvalidateOrders()".

delegate MyOrderCollection MyOrderCollectionAsyncLoadDelegate( bool invalidate );

public IAsyncResult BeginGetOrders( System.AsyncCallback callback )
{
    
return this.BeginGetDocuments( false, callback );
}

public IAsyncResult BeginGetOrders( bool invalidate, System.AsyncCallback callback )
{
    MyOrderCollectionAsyncLoadDelegate handler = 
new MyOrderCollectionAsyncLoadDelegate( this.GetOrders );
    
return handler.BeginInvoke( invalidate, callback, null );
}

public MyOrderCollection EndGetOrders( IAsyncResult result )
{
    
if( result.IsCompleted )
    {
        AsyncResult aRes = result 
as AsyncResult;
        MyOrderCollectionAsyncLoadDelegate handler =  aRes.AsyncDelegate 
as MyOrderCollectionAsyncLoadDelegate;
        
this._myOrders = handler.EndInvoke( result ) as MyOrderCollection;
    }

    
return this._myOrders;
}

public MyOrderCollection GetOrders()
{
    
return this.GetOrders( false );
}

public MyOrderCollection GetOrders( bool invalidate )
{
    
//LazyLoad o se c'è richiesta di reload
    
ifthis._orders == null || invalidate )
    {
        OrdersLazyLoadEventArgs args = 
new OrdersLazyLoadEventArgs();
        
this.OnOrdersLazyLoad( args );
        
this._orders = args.Result;
    }
    
    
return this._orders; 
}
Il codice esposto in questi due articoli è fondamentalment codice "real world" anche se ampiamente rimaneggiato per adattarlo al contesto.
Disponibile per qualsiasi chiarimento e/o approfondimento vi invito ad utilizzare il form contact o a lasciare un commento.
.m