Il presente articolo è stato tradotto automaticamente.

Modelli in esercitazione

Programmazione funzionale per lo sviluppo di .NET ogni giorno

Jeremy Miller

Che cos'è l'avanzamento più importante nell'ecosistema .NET negli ultimi tre o quattro anni? Potrebbe essere tentati di nome una nuova tecnologia o di un framework, come Windows Communication Foundation (WCF) o Windows Presentation Foundation (WPF). Per me personalmente, tuttavia, sarebbe dico che le potenti aggiunte ai linguaggi C# e Visual Basic tramite le ultime due versioni di .NET Framework hanno avuto un impatto più significativo su mio attività quotidiane di sviluppo. In questo articolo si desidera esaminare in particolare come il nuovo supporto per funzionale tecniche di programmazione in .NET 3.5 può aiutarti a eseguire le operazioni seguenti:

  1. Rendere il codice più dichiarativo.
  2. Consente di ridurre gli errori nel codice.
  3. Consente di scrivere meno righe di codice per molte attività comuni.

La funzionalità di LINQ (Language Integrated Query) in tutte le versioni di molti è un utilizzo ovvio e potente di programmazione funzionale in. NET, ma che solo suggerimento del iceberg.

Per mantenere con il tema di "sviluppo quotidiane"È basata la maggior parte dei miei esempi di codice su C# 3.0 con alcuni JavaScript sprinkled in. Notare che alcune delle più recenti, altri linguaggi di programmazione per CLR, quali IronPython, IronRuby e F # dispone di supporto notevolmente più complessa o di sintassi più vantaggioso per le funzionale tecniche di programmazione illustrato in questo articolo. Sfortunatamente, la versione corrente di Visual Basic non supporta funzioni lambda su più righe, molte delle tecniche illustrate di seguito non sono utilizzabili come in Visual Basic. Tuttavia, sarebbero invitare gli sviluppatori Visual Basic da considerare queste tecniche in preparazione per la versione successiva del linguaggio, spedizione in Visual Studio 2010.

Funzioni first-Class

Alcuni elementi di programmazione funzionale sono possibili in C# o Visual Basic poiché disponiamo ora di prim'ordine funzioni che possono essere trasmessi tra metodi, mantenuti come variabili o anche restituito da un altro metodo. Delegati anonimi da .NET 2.0 e l'espressione di lambda più recente di .NET 3.5 sono come C# e Visual Basic implementare funzioni di prim'ordine, ma "Espressione lambda"significa qualcosa di più specifici in informatica. Un altro concetto comune per una funzione di prim'ordine è il "blocco". Per il resto di questo articolo verrà utilizzato il termine "blocco"per indicare le funzioni di prim'ordine, invece di "chiusura"(un tipo specifico della funzione di prim'ordine verranno quindi illustrati) o "Lambda"Per evitare di imprecisioni accidentali (e il wrath di esperti di programmazione funzionali reale). Una chiusura contiene le variabili definite all'esterno della funzione chiusura. Se è stata utilizzata la libreria jQuery sempre più diffuse per lo sviluppo di JavaScript, è stato utilizzato probabilmente chiusure piuttosto frequente. Ecco un esempio dell'utilizzo di una chiusura, ricavata dal progetto corrente:

// Expand/contract body functionality          
var expandLink = $(".expandLink", itemElement);
var hideLink = $(".hideLink", itemElement);
var body = $(".expandBody", itemElement);
body.hide();

// The click handler for expandLink will use the
// body, expandLink, and hideLink variables even
// though the declaring function will have long
// since gone out of scope.
expandLink.click(function() {
    body.toggle();
    expandLink.toggle();
    hideLink.toggle();
});

Questo codice viene utilizzato per impostare un effetto accordion piuttosto tipico per visualizzare o nascondere le pagine Web contenuto facendo clic su un < un >elemento. Definiamo il gestore click dell'expandLink passando una funzione di chiusura che utilizza le variabili create all'esterno della chiusura. La funzione che contiene le variabili e il gestore click terminerà molto prima che l'expandLink può essere selezionato dall'utente, ma sarà possibile utilizzare le variabili di corpo e hideLink il gestore di fare clic su.

Lambdas come dati

In alcuni casi, è possibile utilizzare la sintassi della lambda per rappresentare un'espressione di codice che può essere utilizzato come dati anziché eseguire. Non mi particolarmente l'istruzione che più volte è possibile leggere, il primo, quindi seguito è riportato un esempio di trattare un lambda come dati da un mapping relazionale esplicita/oggetti utilizzando la libreria NHibernate Fluent:

 

public class AddressMap : DomainMap<Address>
    {
        public AddressMap()
        {
            Map(a => a.Address1);
            Map(a => a.Address2);
            Map(a => a.AddressType);
            Map(a => a.City);
            Map(a => a.TimeZone);
            Map(a => a.StateOrProvince);
            Map(a => a.Country);
            Map(a => a.PostalCode);
        }
    }

NHibernate abbiano una conoscenza approfondita non valuta l'espressione a = >a.Address1. In alternativa, analizza l'espressione per trovare il nome Address1 da utilizzare per il mapping NHibernate sottostante. Questa tecnica è distribuire ampiamente attraverso molti progetti open source recenti nello spazio .NET. Utilizzare le espressioni lambda solo per oggetti PropertyInfo e nomi di proprietà viene spesso definita reflection statica.

Blocchi di passaggio

Uno dei motivi migliori per studiare programmazione funzionale è ulteriori funzioni come prima classe consentono di ridurre la duplicazione di codice fornendo un meccanismo più specifico per la composizione rispetto alla classe. Spesso occuperanno tra le sequenze di codice che sono essenzialmente identiche nella loro forma except for un passaggio in un punto qualsiasi in the middle of la sequenza di base. Con la programmazione orientata agli oggetti, è possibile utilizzare l'ereditarietà con il criterio di metodo modello per tentare di eliminare la duplicazione. Più è possibile trovare tale passaggio impedisce il passaggio al centro variabile che rappresenta un altro metodo che implementa la sequenza di base da un metodo di pulitura per eliminare la duplicazione.

Uno dei modi migliori per rendere un'API più semplici da utilizzare e meno soggetta a errori è di ridurre il codice ripetitivo. Si consideri, ad esempio, caso comune di un'API che consente di accedere a un servizio remoto o una risorsa, quale un oggetto ADO.NET IDbConnection o un listener del socket che richiede una connessione permanente o con informazioni sullo stato. È necessario in genere "Apri"la connessione prima di utilizzare la risorsa. Tali connessioni con informazioni sullo stato sono spesso costose o limitate in termini di risorse, pertanto è spesso importante per "Chiudi"la connessione non appena terminato per rilasciare la risorsa per altri processi o thread.

Nel codice riportato di seguito viene illustrato un'interfaccia rappresentativa per il gateway a una connessione con informazioni sullo stato di un tipo:

 

public interface IConnectedResource
    {
        void Open();
        void Close();

        // Some methods that manipulate the connected resource
        BigDataOfSomeKind FetchData(RequestObject request);
        void Update(UpdateObject update);
    }

Ogni singola volta che un'altra classe utilizza questa interfaccia IConnectedResource, il metodo Open deve essere chiamato prima di utilizzare qualsiasi altro metodo e il metodo Close deve sempre essere chiamato in seguito, come illustrato in figura 1.

In un precedente articolo ho parlato il concetto di essenza e cerimonia nella nostra progettazione. (Vedere msdn.microsoft.com/magazine/dd419655.aspx di). "[NULL] L'essenza di"di ConnectedSystemConsumer la responsabilità della classe non è semplicemente utilizzare la risorsa collegata per aggiornare alcune informazioni. Sfortunatamente, gran parte del codice in ConnectedSystemConsumer riguarda "cerimonia"di connessione e disconnessione dall'interfaccia IConnectedResource e la gestione degli errori.

Figura 1 con IConnectedResource

 

public class ConnectedSystemConsumer
{
private readonly IConnectedResource _resource;
public ConnectedSystemConsumer(IConnectedResource resource)
{
_resource = resource;
}
public void ManipulateConnectedResource()
{
try
{
// First, you have to open a connection
_resource.Open();
// Now, manipulate the connected system
_resource.Update(buildUpdateMessage());
}
finally
{
_resource.Close();
}
}
}

Peggio ancora è il fatto che la "try, aprire o eseguire stuff/infine/chiusura"Cerimonia di codice deve essere duplicato per ogni utilizzo dell'interfaccia IConnectedResource. Come ho già spiegato prima, uno dei modi migliori per migliorare la struttura è per contrassegnare uscita duplicati ogni volta che creeps nel codice. Si proverà a un approccio diverso per l'API IConnectedResource utilizzando un blocco o chiusura. In primo luogo, sto per applicare il principio di isolamento di interfaccia (vedere objectmentor.com/resources/articles/isp.pdf per ulteriori informazioni) per estrarre un'interfaccia esclusivamente per richiamare la risorsa collegata senza i metodi per l'apertura o chiusura:

 

public interface IResourceInvocation
    {
        BigDataOfSomeKind FetchData(RequestObject request);
        void Update(UpdateObject update);
    }

Successivamente, È possibile creare una seconda interfaccia che viene utilizzata esclusivamente per ottenere accesso alla risorsa connessa rappresentata dall'interfaccia IResourceInvocation:

 

public interface IResource
    {
        void Invoke(Action<IResourceInvocation> action);
    }

A questo punto, è possibile riscrivere la classe ConnectedSystemConsumer per utilizzare l'API più recente, di stile funzionali:

 

public class ConnectedSystemConsumer
    {
        private readonly IResource _resource;
 
        public ConnectedSystemConsumer(IResource resource)
        {
            _resource = resource;
        }

        public void ManipulateConnectedResource()
        {
            _resource.Invoke(x =>
            {
                x.Update(buildUpdateMessage());
            });
        }
    }

Questa nuova versione di ConnectedSystemConsumer non ha più attenzione su come impostare o eliminare la risorsa collegata. In effetti, ConnectedSystemConsumer indica solo l'interfaccia IResource per "go up per il primo IResourceInvocation viene visualizzata e assegnare queste istruzioni"passando in un blocco o chiusura al metodo IResource.Invoke. Che ripetitiva "try, aprire o eseguire stuff/infine/chiusura"il codice cerimonia lamentano sulla prima si è ora nell'implementazione concreta di IResource, come illustrato in di figura 2.

Nella figura 2 concreto di implementazione di IResource

 

frepublic
class Resource : IResource
{
public void Invoke(Action<IResourceInvocation> action)
{
IResourceInvocation invocation = null;
try
{
invocation = open();
// Perform the requested action
action(invocation);
}
finally
{
close(invocation);
}
}
private void close(IResourceInvocation invocation)
{
// close and teardown the invocation object
}
private IResourceInvocation open()
{
// acquire or open the connection
}
}

Potrebbe sostenere che sono stati migliorati la progettazione e l'utilizzabilità dell'API inserendo la responsabilità di apertura e chiusura la connessione alla risorsa esterna alla classe di risorse. Si è migliorata anche la struttura del codice mediante l'incapsulamento dei dettagli di problemi di infrastruttura dal flusso di lavoro principale dell'applicazione. La seconda versione di ConnectedSystemConsumer SA molto minore sul funzionamento della risorsa connessa esterna è stata la prima versione. La seconda struttura consente di modificare più facilmente come il sistema si interagisce con la risorsa connessa esterna senza modifica e potenzialmente destabilizzare il codice del flusso di lavoro di base del sistema.

La seconda struttura consente inoltre il sistema meno tendente all'errore eliminando la duplicazione di "try/Apri/infine/chiusura"ciclo. Ogni volta che uno sviluppatore deve ripetere tale codice, ha rischi che effettua una codifica che considerano per errore Impossibile tecnicamente funzionare correttamente ma esaurire le risorse e danneggiare le caratteristiche di scalabilità dell'applicazione.

Esecuzione ritardata

Uno dei concetti più importanti per comprendere sulla programmazione funzionale è esecuzione ritardata. Fortunatamente, questo concetto è anche relativamente semplice. Tutte è che è stata definita una funzione di blocco inline non necessariamente esegue immediatamente. Esaminiamo un utilizzo pratico di esecuzione ritardata.

In un'applicazione WPF piuttosto grande, È possibile utilizzare un'interfaccia indicatore chiamata IStartable per denotare servizi che devono essere, beh, avviato come parte del processo di avvio automatico dell'applicazione.

 

public interface IStartable
    {
        void Start();
    }

Tutti i servizi per questa particolare applicazione registrati e recuperati dall'applicazione da un contenitore di inversione del controllo (in questo caso, StructureMap). All'avvio dell'applicazione, È necessario che il bit seguente di codice per individuare in modo dinamico tutti i servizi nell'applicazione che devono essere avviato e quindi avviati:

 

// Find all the possible services in the main application
// IoC Container that implements an "IStartable" interface
List<IStartable> startables = container.Model.PluginTypes
    .Where(p => p.IsStartable())
    .Select(x => x.ToStartable()).ToList();
         

// Tell each "IStartable" to Start()
startables.Each(x => x.Start());

Esistono tre le espressioni lambda nel codice. Si supponga che è collegata la copia di codice sorgente completo della libreria di classi base .NET questo codice e quindi cercato per il passaggio attraverso di esso con il debugger. Quando si tenta di passaggio in WHERE, SELECT o Each chiamate, si potrebbe notare che le espressioni lambda non sono le righe successiva di codice da eseguire e come questi metodi è scorrere le strutture interne del membro container.Model.PluginTypes, le espressioni lambda sono tutte di eseguita più volte. Per considerare esecuzione ritardata è che quando si richiama il metodo ogni, si sta solo indica l'ogni metodo le operazioni da eseguire ogni volta che si tratta in un oggetto IStartable.

Memoization

Memoization è una tecnica di ottimizzazione utilizzata per evitare l'esecuzione di chiamate di funzione dispendiose riutilizzando i risultati dell'esecuzione precedente con lo stesso input. Ho trovato prima in contatto con memoization il termine in relazione alla programmazione funzionale con F #, ma nel corso della ricerca in questo articolo ho capito che il team utilizza spesso memoization nella fase di sviluppo C#. Si supponga che spesso è necessario recuperare una sorta di calcolato dati finanziari per una determinata area con un servizio simile al seguente:

 

public interface IFinancialDataService
    {
        FinancialData FetchData(string region);
    }

IFinancialDataService accade ai tempi estremamente lunghi per l'esecuzione e i dati finanziari sono abbastanza statici, in, applicazione memoization sarà molto utile per i tempi di risposta dell'applicazione. È possibile creare un'implementazione del wrapper di IFinancialDataService che implementa memoization per una classe interna IFinancialDataService, come illustrato in di figura 3.

Nella figura 3 implementazione di una classe IFinancialDataService interna

 

public class MemoizedFinancialDataService : IFinancialDataService
{
private readonly Cache<string, FinancialData> _cache;
// Take in an "inner" IFinancialDataService object that actually
// fetches the financial data
public MemoizedFinancialDataService(IFinancialDataService
innerService)
{
_cache = new Cache<string, FinancialData>(region =>
innerService.FetchData(region));
}
public FinancialData FetchData(string region)
{
return _cache[region];
}
}

La cache < TKey, TValue >classe è semplicemente un wrapper intorno a un dizionario < TKey, TValue >oggetto. Nella figura 4 viene illustrata parte della classe cache.

Figura 4 la classe cache

public class Cache<TKey, TValue> : IEnumerable<TValue> where TValue :
class
{
private readonly object _locker = new object();
private readonly IDictionary<TKey, TValue> _values;
private Func<TKey, TValue> _onMissing = delegate(TKey key)
{
string message = string.Format(
"Key '{0}' could not be found", key);
throw new KeyNotFoundException(message);
};
public Cache(Func<TKey, TValue> onMissing)
: this(new Dictionary<TKey, TValue>(), onMissing)
{
}
public Cache(IDictionary<TKey, TValue>
dictionary, Func<TKey, TValue>
onMissing)
: this(dictionary)
{
_onMissing = onMissing;
}
public TValue this[TKey key]
{
get
{
// Check first if the value for the requested key
// already exists
if (!_values.ContainsKey(key))
{
lock (_locker)
{
if (!_values.ContainsKey(key))
{
// If the value does not exist, use
// the Func<TKey, TValue> block
// specified in the constructor to
// fetch the value and put it into
// the underlying dictionary
TValue value = _onMissing(key);
_values.Add(key, value);
}
}
}
return _values[key];
}
}
}

Se si è interessati i meccanismi interni della classe cache, è possibile trovare una versione nella finestra di diversi progetti open source software, inclusi FubuMVC StructureMap, StoryTeller, e, credo, NHibernate Fluent.

Il modello Mappa/ridurre

Ho scoperto che molte attività di sviluppo comuni sono più semplici con tecniche di programmazione funzionale. In particolare, elenco e le operazioni di impostazione nel codice sono molto più semplice meccanicamente in linguaggi che supportano il modello “ ridurre/mappa ”. (In LINQ, “ mappa ” è “ selezionare ” e “ ridurre ” è “ aggregate ”). Pensare a come è possibile calcolare la somma di una matrice di valori integer. In .NET 1.1, era necessario scorrere la matrice qualcosa di simile:

i numeri di tipo int [] = new int di [] {1,2,3,4,5};
somma di int = 0;
per (int i = 0;i <numbers.Length;i ++)
{
Somma i numeri di += [i];
}

Console.WriteLine(SUM);

La serie di miglioramenti del linguaggio per supportare LINQ in .NET 3.5 fornito le funzionalità di ridurre o mappa comune nei linguaggi di programmazione funzionale. Oggi, il codice sopra riportato potrebbe essere semplicemente scritto come:

i numeri di tipo int [] = new int di [] {1,2,3,4,5};
somma di int = numbers.Aggregate ((x, y) = >x + y);

o più semplicemente come:

somma di int = numbers.Sum();
Console.WriteLine(SUM);

Continuazioni di

Inserire circa, una continuazione nella programmazione è un'astrazione di qualche tipo che indica "operazioni da eseguire successivamente"oppure la parte "rimanente del calcolo." In alcuni casi è importante per completare la parte del processo di calcolo in un altro momento, come in un'applicazione di procedura guidata in cui un utente in modo esplicito può consentire il passaggio successivo o annullare l'intero processo.

È possibile passare direttamente in un esempio di codice. Si supponga che si sta sviluppando un'applicazione desktop in Windows Form o WPF. Spesso è necessario avviare un tipo di processo a esecuzione prolungata o accedere a un servizio esterno lento da un'azione di schermo. Per ragioni di semplicità di utilizzo, certamente non si desidera bloccare l'interfaccia utente e renderla non risponde durante la chiamata del servizio in corso, in modo è possibile eseguirla in un thread in background. Quando la chiamata del servizio viene infine restituito, si desideri aggiornare l'interfaccia utente con i dati proveniente dal servizio, ma come qualsiasi esperti di Windows Form o WPF sviluppatore sa, è possibile aggiornare elementi dell'interfaccia utente solo nel thread di interfaccia dell'utente principale.

È certamente possibile utilizzare la classe BackgroundWorker dello spazio dei nomi System.ComponentModel, ma preferisco un approccio diverso basato sul passaggio le espressioni lambda in un oggetto CommandExecutor, rappresentato da questa interfaccia:

public interface ICommandExecutor
    {
        // Execute an operation on a background thread that
        // does not update the user interface
        void Execute(Action action);

        // Execute an operation on a background thread and
        // update the user interface using the returned Action
        void Execute(Func<Action> function);
    }

Il primo metodo è semplicemente un'istruzione per eseguire un'attività in un thread in background.Il secondo metodo che accetta una funzione < azione >è più interessante.Diamo un'occhiata come questo metodo viene in genere utilizzato codice dell'applicazione.

Innanzitutto, si supponga che si sta strutturare il codice di Windows Form o WPF con il modulo controller supervisione del modello Model View Presenter.(Vedere msdn.microsoft.com/magazine/cc188690.aspx per ulteriori informazioni sul modello MVP). In questo modello, la classe Presenter sarà responsabile della chiamata in un metodo di servizio a esecuzione prolungata e utilizzando i dati restituiti per aggiornare la visualizzazione.La nuova classe Presenter utilizzerà semplicemente l'interfaccia di ICommandExecutor illustrata precedentemente per gestire tutti i il threading e thread di marshalling di lavoro, come illustrato in di figura 5.

Figura 5 la classe Presenter

public class Presenter
{
private readonly IView _view;
private readonly IService _service;
private readonly ICommandExecutor _executor;
public Presenter(IView view, IService service, ICommandExecutor
executor)
{
_view = view;
_service = service;
_executor = executor;
}
public void RefreshData()
{
_executor.Execute(() =>
{
var data = _service.FetchDataFromExtremelySlowServiceCall();
return () => _view.UpdateDisplay(data);
});
}
}

La classe Presenter richiama ICommandExecutor.Execute passando in un blocco che restituisce un altro blocco. Il blocco originale richiama la chiamata a esecuzione prolungata del servizio per ottenere alcuni dati e restituisce un blocco di continuazione che può essere utilizzato per aggiornare l'interfaccia utente (IView in questo scenario). In questo caso specifico, è importante utilizzare l'approccio di continuazione invece solo l'aggiornamento il IView allo stesso tempo perché l'aggiornamento è possibile eseguire il marshalling al thread dell'interfaccia utente.

Nella figura 6 mostrata l'implementazione concreta dell'interfaccia ICommandExecutor.

Nella figura 6 concreto di implementazione di ICommandExecutor

public class AsynchronousExecutor : ICommandExecutor
{
private readonly SynchronizationContext _synchronizationContext;
private readonly List<BackgroundWorker> _workers =
new List<BackgroundWorker>();
public AsynchronousExecutor(SynchronizationContext
synchronizationContext)
{
_synchronizationContext = synchronizationContext;
}
public void Execute(Action action)
{
// Exception handling is omitted, but this design
// does allow for centralized exception handling
ThreadPool.QueueUserWorkItem(o => action());
}
public void Execute(Func<Action> function)
{
ThreadPool.QueueUserWorkItem(o =>
{
Action continuation = function();
_synchronizationContext.Send(x => continuation(), null);
});
}
}

Il metodo Execute (funzione < azione >) richiama la funzione < azione >in un thread in background e quindi accetta degli continuazione (restituito dalla funzione < azione > azione) utilizza un SynchronizationContext oggetti e di eseguire la continuazione nel thread dell'interfaccia utente principale.

Mi piace passando blocchi nell'interfaccia ICommandExecutor a causa della modalità poco codice ceremonial occorrono per richiamare le elaborazioni in background. In un precedente produzione di questo approccio, prima era le espressioni lambda o delegati anonimi in C#, ho avuto un'implementazione simile che utilizzata di classi di schema Command poco simile a quello riportato di seguito:

public interface ICommand
    {
        ICommand Execute();
    }

    public interface JeremysOldCommandExecutor
    {
        void Execute(ICommand command);
    }

Lo svantaggio dell'approccio precedente è che ho dovuto scrivere ulteriori classi di comando solo per modellare l'operazione in background e il codice di aggiornamento per la visualizzazione. Le funzioni di dichiarazione e il costruttore di classe aggiuntiva sono un po' più codice di cerimonia, che è possibile eliminare con l'approccio funzionale, ma più importante per me è che l'approccio funzionale consente di inserire tutto questo codice strettamente correlato in una singola posizione nel Presenter anziché ingrandire su queste classi di comando poco.

Stile del passaggio di continuazione

Creazione sul concetto di continuazione, è possibile utilizzare lo stile di passaggio di continuazione della programmazione per richiamare un metodo passando una continuazione anziché attendere che il valore restituito del metodo. Il metodo che accetta la continuazione è responsabile di decidere se e quando per chiamare la continuazione.

In un progetto Web MVC corrente, esistono numerose operazioni di controller che salvare gli aggiornamenti dall'input dell'utente inviato dal browser client tramite una chiamata di AJAX a un oggetto di entità di dominio. La maggior parte di queste azioni di controller di richiamano semplicemente la classe di repository per salvare l'entità modificato, ma altre azioni consente di utilizzare altri servizi per eseguire le operazioni di persistenza. (Vedere il mio articolo nel numero di aprile di MSDN Magazine in msdn.microsoft.com/magazine/dd569757.aspx per la classe di repository ulteriori informazioni).

Il flusso di lavoro base di queste azioni controller è coerenza:

  1. Convalidare l'entità di dominio e registrare gli errori di convalida.
  2. Se sono presenti errori di convalida, restituire una risposta al client che indica che l'operazione non è riuscita e includere gli errori di convalida per la visualizzazione sul client.
  3. Se sono presenti meno errori di convalida, mantenere l'entità di dominio e restituire una risposta al client che indica che l'operazione è riuscita.

Che si desidera eseguire è centralizzare il flusso di lavoro base ma consentire ogni azione di singoli controller per variare come viene eseguita la persistenza effettiva. Oggi mio team è ciò ereditando da una CrudController < T >superclasse con una grande quantità di metodi di modello che ogni sottoclasse può eseguire l'override per aggiungere o modificare il comportamento di base. Questa strategia ha lavorato anche in prima, ma è suddividere rapidamente come hanno migliorato le variazioni. Ora il mio team sta per spostarsi mediante la nostra azioni controller delegare simile l'interfaccia seguente passaggio di continuazione codice di stile:

 

public interface IPersistor
    {
        CrudReport Persist<T>(T target, Action<T> saveAction);
        CrudReport Persist<T>(T target);
    }

Azione di un tipico controller dovrebbe indicare IPersistor per eseguire il flusso di lavoro CRUD base e fornire una continuazione utilizzato IPersistor per salvare effettivamente l'oggetto di destinazione. Nella figura 7 viene illustrato un azione di controller di esempio che richiama IPersistor ma utilizza un servizio diverso dal repository di base per la persistenza effettiva.

Nella figura 7 A di azione del controller di esempio

public class SolutionController
{
private readonly IPersistor _persistor;
private readonly IWorkflowService _service;
public SolutionController(IPersistor persistor, IWorkflowService
service)
{
_persistor = persistor;
_service = service;
}
// UpdateSolutionViewModel is a data bag with the user
// input from the browser
public CrudReport Create(UpdateSolutionViewModel update)
{
var solution = new Solution();
// Push the data from the incoming
// user request into the new Solution
// object
update.ToSolution(solution);
// Persist the new Solution object, if it's valid
return _persistor.Persist(solution, x => _service.Create(x));
}
}

Penso che la cosa importante da notare è la che IPersistor stesso è decidere se e quando verrà chiamato fornito da SolutionController la continuazione. Figura 8 viene illustrata un'implementazione di esempio di IPersistor.

Figura 8 un'implementazione di IPersistor

public class Persistor : IPersistor
{
private readonly IValidator _validator;
private readonly IRepository _repository;
public Persistor(IValidator validator, IRepository repository)
{
_validator = validator;
_repository = repository;
}
public CrudReport Persist<T>(T target, Action<T> saveAction)
{
// Validate the "target" object and get a report of all
// validation errors
Notification notification = _validator.Validate(target);
// If the validation fails, do NOT save the object.
// Instead, return a CrudReport with the validation errors
// and the "success" flag set to false
if (!notification.IsValid())
{
return new CrudReport()
{
Notification = notification,
success = false
};
}
// Proceed to saving the target using the Continuation supplied
// by the client of IPersistor
saveAction(target);
// return a CrudReport denoting success
return new CrudReport()
{
success = true
};
}
public CrudReport Persist<T>(T target)
{
return Persist(target, x => _repository.Save(x));
}
}

Scrivere meno codice

È stato originariamente scelto in questa sezione perché è interessato a ulteriori informazioni su programmazione funzionale e come può essere applicato anche all'interno di C# o Visual Basic. Nel corso di scrittura in questo articolo, è appreso molto sulle tecniche di programmazione funzionale come utile può essere in attività normale. La conclusione più importante è raggiunto e che cosa si è provato trasmettere in questo caso, è che confrontato con altre tecniche, funzionale programmazione approcci spesso può creare meno codice e spesso più codice dichiarativo per alcune attività di scrittura, e questo è quasi sempre una cosa positiva.

Jeremy Miller, un Microsoft MVP per C#, è anche l'autore dello strumento open source StructureMap (structuremap.sourceforge.net) per l'inserimento delle dipendenze con .NET e lo strumento di (storyteller.tigris.org) StoryTeller prossima per la verifica le FIT in .NET. Visitare il suo blog The Shade Tree Developer in codebetter.com/blogs/jeremy.miller, parte del sito CodeBetter.