Dieser Artikel wurde maschinell übersetzt.

Muster in der Praxis

Funktionale Programmierung für die .NET-Entwicklung Everyday

Jeremy Miller

Was ist der größte Fortschritt in .NET Ökosystem, in den vergangenen drei oder vier Jahren? Möglicherweise werden versucht, eine neue Technologie oder Frameworks wie Windows Communication Foundation (WCF) oder Windows Presentation Foundation (WPF). Für mich würde persönlich jedoch ich sagen, dass die leistungsfähigen Ergänzungen zu den Programmiersprachen c# und Visual Basic über die letzten beiden Versionen von .NET Framework weit mehr erhebliche Auswirkungen auf Meine tägliche Entwicklungsaktivitäten hatten. In diesem Artikel möchte ich, wie die neue Unterstützung für funktionale Programmiertechniken in .NET 3.5 Ihnen Folgendes helfen kann, insbesondere zu untersuchen:

  1. Stellen Sie Ihren Code mehr deklarative.
  2. Verringern von Fehlern im Code.
  3. Schreiben Sie weniger Codezeilen für viele allgemeine Aufgaben.

Das Feature Language Integrated Query (LINQ) in allen seinen viele Variationen ist eine offensichtliche und leistungsfähige Verwendung von funktionalen Programmierung in .NET, aber das ist einfach Tipp der des Eisbergs.

Mit dem Thema "alltäglichen Entwicklung" beibehaltenC# 3.0 mit einigen sprinkled in JavaScript haben den Großteil meiner Codebeispiele abhängig. Beachten Sie, dass einige weitere, neueren Programmiersprachen für die CLR wie IronPython, IronRuby und F#-erheblich stärker Unterstützung oder mehr vorteilhaft Syntax für die funktionalen Programmiertechniken, die in diesem Artikel gezeigten verfügen. Leider unterstützt nicht die aktuelle Version von Visual Basic mehrzeilige Lambda-Funktionen, so viele der Techniken gezeigt hier sind nicht als verwendbar in Visual Basic. Jedoch würde ich Visual Basic-Entwickler sollten Sie diese Techniken in Vorbereitung auf die nächste Version der Sprache, geraten, in Visual Studio 2010.

First-Class-Funktion

Einige Elemente der funktionalen Programmierung sind in c# oder Visual Basic da wir jetzt erstklassige Funktionen verfügen, die zwischen Methoden, um übergeben werden können als Variablen gespeichert oder sogar von einer anderen Methode zurückgegeben. Anonyme Delegaten von .NET 2.0 und neuere Lambda-Ausdruck in .NET 3.5 sind wie c# und Visual Basic erstklassige Funktionen, aber "Lambda-Ausdruck" Implementierenbedeutet, dass etwas genauer in Informatik. Ein anderer allgemeiner Begriff für eine erstklassige Funktion ist "Blockieren". Für den Rest dieses Artikels werde ich den Begriff "Blockieren" verwenden.kennzeichnen Sie erstklassige Funktionen, anstatt "Abschluss"(eine bestimmte Art erstklassige Funktion als nächstes erläutert) oder "Lambda-"um unbeabsichtigte Ungenauigkeiten (und die Wrath von echten funktionalen Programmierung Experten) zu vermeiden. Eine Closure enthält Variablen, die von außerhalb der Closure-Funktion definiert. Wenn Sie zunehmend beliebter jQuery Bibliothek für die Entwicklung von JavaScript verwendet haben, haben Sie Betriebsferien wahrscheinlich sehr häufig verwendet. Hier ist ein Beispiel für die Verwendung einer Closure, meine aktuelle Projekt entnommen:

// 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();
});

Dieser Code wird verwendet, um einen recht typische Accordion Effekt So blenden Sie Inhalte auf unsere Webseiten durch Klicken auf eine < a > oder festzulegenElement. Wir definieren den Click-Ereignishandler von der ExpandLink durch die Übergabe einer Closure-Funktion, die außerhalb der Closure erstellte Variablen verwendet. Die Funktion, die die Variablen und die Click-Ereignishandler enthält wird beendet, lange bevor die ExpandLink vom Benutzer geklickt werden kann, aber der Click-Ereignishandler werden weiterhin die Variablen Rumpf und HideLink verwenden können.

Lambdas als Daten

In einigen Fällen können Sie die Lambda-Syntax, um einen Ausdruck in Code zu kennzeichnen, die kann als Daten verwendet, sondern werden ausgeführt. Ich weiß nicht besonders, die Anweisung die erste, die mehrmals wird in der er gelesen, deshalb wenden wir uns ein Beispiel für einen Lambda-aus eine explizite Objekt/relationalen Zuordnung unter Verwendung der Fluent NHibernate-Bibliothek als Daten behandelt:

 

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);
        }
    }

Bestens auskennen NHibernate ist nie der Ausdruck a = >a.Address1. Stattdessen analysiert die Ausdrucks zum Suchen des Namens Adresse1 in der zugrunde liegenden NHibernate-Zuordnung verwenden. Diese Technik verfügt über viele aktuellen Open-Source-Projekte im Bereich .NET weit verteilt. Mit Lambda-Ausdrücke nur bei PropertyInfo-Objekten und Eigenschaftennamen abgerufen wird häufig statische Reflektion aufgerufen.

Übergabe Blöcke

Eine der besten Gründe für den funktionalen Programmierung untersuchen besteht darin, erfahren Sie, wie erstklassige Funktionen können Sie durch Bereitstellen einer einen besser abgestimmte Mechanismus für die Komposition als die Klasse Duplizität im Code zu verringern. Sie kommen häufig über Sequenzen von Code, die in Ihrer grundlegenden Form except for eine Schritt in the middle of der Sequenz irgendwo im Wesentlichen identisch sind. Vererbung mit dem Muster Vorlage-Methode können Sie mit der objektorientierten Programmierung versuchen, die Duplizierung zu vermeiden. Mehr und mehr auffindbar, Übergabe blockiert, die die Variable Schritt in der Mitte zu einer anderen Methode, die die grundlegende Sequenz, die eine sauberere Möglichkeit, diese Duplizierung vermeiden implementiert darstellt.

Eine der besten Möglichkeiten, um eine API mit einfacher und weniger fehleranfällig machen besteht darin, sich wiederholende Code zu verringern. Genommen Sie an, den allgemeinen Fall einer API entwickelt, um Zugriff auf eine remote-Dienst oder eine Ressource wie ein ADO.NET IDbConnection-Objekt oder ein Socketlistener, der eine statusbehaftete oder permanente Verbindung erfordert. Sie müssen in der Regel "Öffnen"die Verbindung vor der Verwendung der Ressource. Diese Verbindungen mit Statusinformationen sind oft aufwendige oder knappe hinsichtlich der Ressourcen, daher ist es häufig wichtig, "Schließen"die Verbindung, sobald Sie fertig sind, die Ressource für andere Prozesse oder Threads freizugeben.

Der folgende Code zeigt eine repräsentative Schnittstelle für das Gateway zu einer statusbehafteten Verbindung eines Typs:

 

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

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

Einer anderen Klasse diese Schnittstelle IConnectedResource verbraucht jedem einzelnen die Open-Methode verwendet aufgerufen werden, bevor Sie andere Methode verwenden, und die Close-Methode sollte immer aufgerufen werden danach wie in Abbildung 1 dargestellt.

In einem früheren Artikel erörtert das Konzept der wesentlichen gegenüber Zeremonie in unsere Entwürfe. (Siehe msdn.microsoft.com/magazine/dd419655.aspx). "Wesentlichen"Die ConnectedSystemConsumer ist Klasse Verantwortung einfach verbundene Ressource verwenden, um einige Informationen zu aktualisieren. Leider Großteil des Codes in ConnectedSystemConsumer befasst sich mit "Zeremonie"Herstellen einer Verbindung zu, und trennen die IConnectedResource-Schnittstelle und Fehlerbehandlung.

Abbildung 1 mit 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();
}
}
}

Schlimmer noch ist die Tatsache, die "versuchen Sie, öffnen/Do Dinge/finally/schließen"Zeremonie Code muss für jede Verwendung der IConnectedResource-Schnittstelle dupliziert werden. Wie erwähnt vor haben, ist eine der besten Möglichkeiten, Ihren Entwurf verbessern um Duplizierung zu Stempeln, wo es in Ihren Code creeps. Wir versuchen, einen anderen Ansatz für die IConnectedResource-API mit einem Block oder Betriebsferien. Zunächst werde ich dem Prinzip der Schnittstelle Trennung anwenden (objectmentor.com/resources/articles/isp.pdf für Weitere Informationen siehe), eine Schnittstelle ausschließlich für verbundene Ressource ohne die Methoden für das Öffnen oder schließen aufrufen zu extrahieren:

 

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

Als Nächstes erstellen Sie eine zweite Schnittstelle, ausschließlich mit dem Zugriff auf die verbundenen dargestellte Ressource von der IResourceInvocation-Schnittstelle:

 

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

Nun, schreiben wir die ConnectedSystemConsumer-Klasse, die neuere, funktionsfähigen Style-API verwendet:

 

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

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

Diese neue Version des ConnectedSystemConsumer muss nicht mehr zum Einrichten oder Beenden der verbundenen Ressource interessieren. Tatsächlich weist ConnectedSystemConsumer nur die IResource-Schnittstelle "zum ersten IResourceInvocation Gehe richten Sie sehen und geben Sie diese Anweisungen"durch die Übergabe einen Block oder Betriebsferien der IResource.Invoke-Methode. Alle sich wiederholenden "versuchen Sie, öffnen/Do Dinge/finally/schließen"Zeremonie Code, der ich über vor beschweren wurde ist jetzt im konkrete Implementierung IResource, wie in Abbildung 2 dargestellt.

Abbildung 2 konkreten Implementierung von 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
}
}

Ich würde argumentieren, dass wir unsere Entwurfs- und API Benutzerfreundlichkeit verbessert haben indem die Verantwortung für das Öffnen und schließen die Verbindung mit der externen Ressource in der Resource-Klasse. Wir haben auch die Struktur von unserem Code durch kapselt die Details der Infrastruktur Aspekte aus der zentralen Workflow der Anwendung verbessert. Die zweite Version der ConnectedSystemConsumer weiß weit weniger über die Funktionsweise der externen Ressource verbundenen als die erste Version hat. Das zweite Design können Sie einfacher ändern Ihr System mit externen verbundene Ressource Interaktion ohne ändern und potenziell Destabilisierung der Workflowcode Kern des Systems.

Der zweite Entwurf macht auch Ihr System weniger fehleranfällig durch beseitigen die Duplizierung der "Try/öffnen/finally/schließen"Zyklus. Jedes Mal ein Entwickler hat, dass Code wiederholen, konnte er Risiken machen eine Codierung, verwechselt technisch ordnungsgemäß jedoch erschöpfen Ressourcen und die Skalierbarkeit Merkmalen der Anwendung beschädigen.

Verzögerte Ausführung

Einer der wichtigsten Konzepte zum Verständnis von funktionalen Programmierung ist verzögerte Ausführung. Dieses Konzept ist glücklicherweise auch relativ einfach. Es bedeutet, eine Block-Funktion ist Sie definiert haben Inline nicht unbedingt sofort ausführen. Betrachten wir nun eine praktische Verwendung der verzögerten Ausführung.

In eine relativ große WPF-Anwendung verwende ich eine Markierungsschnittstelle, die IStartable aufgerufen, um Dienste zu kennzeichnen, die auch als Teil der Anwendung bootstrapping Prozesses gestartet werden, müssen.

 

public interface IStartable
    {
        void Start();
    }

Alle Dienste für diese bestimmten Anwendung sind registriert und von der Anwendung von einem Container Inversion of Control (in diesem Fall StructureMap) abgerufen. Beim Start der Anwendung muss das folgende Bit Code dynamisch zu erkennen alle Dienste in der Anwendung, die gestartet werden müssen, und starten Sie Sie:

 

// 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());

Es gibt drei Lambda-Ausdrücke in diesem Code. Angenommen, Sie die vollständige Quelle Code Kopie für die .NET Basisklasse Bibliothek mit diesem Code verbunden und versuchte dann zu Schritt durch mit dem Debugger. Wenn Sie versuchen, in der WHERE, SELECT oder Each Schritt aufgerufen wird, Sie würde bemerken, dass der Lambda-Ausdrücke nicht die nächsten Codezeilen zum Ausführen sind und mehrere Male, wie diese Methoden die internen Strukturen des Members container.Model.PluginTypes durchlaufen, Lambda-Ausdrücke alle werden ausgeführt. Eine weitere Möglichkeit verzögerte Ausführung denken, ist Wenn Sie jede Methode aufrufen, sind Sie nur der jede Methode informiert Vorgehensweise jederzeit es über ein IStartable-Objekt stammt.

Memoization

Memoization ist eine Ausführung teuer Funktionsaufrufe wiederverwenden die Ergebnisse der vorherigen Ausführung mit der gleichen Eingabe zu vermeiden, verwendete Optimierung Technik. Ich zunächst in Kontakt mit den Begriff Memoization in Bezug auf funktionalen Programmierung mit f# stammt, aber im Verlauf des in diesem Artikel untersucht ich erkannte, dass mein Team häufig Memoization in unserer C#-Entwicklung verwendet. Angenommen, müssen Sie häufig eine Art von berechneten Finanzdaten für einen bestimmten Bereich mit einem Dienst wie folgt abrufen:

 

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

IFinancialDataService geschieht extrem langsam ausgeführt werden und die finanziellen Daten relativ statisch, daher anwenden Memoization für Reaktionsfähigkeit der Anwendung sehr nützlich. Sie konnte eine Wrapperimplementierung von IFinancialDataService erstellen, die Memoization für eine innere Klasse IFinancialDataService implementiert als in Abbildung 3.

Abbildung 3 implementieren eine interne IFinancialDataService-Klasse

 

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];
}
}

Der Cache < TKey, TValue >Klasse selbst ist nur ein Wrapper für ein Dictionary < TKey, TValue >Objekt. Abbildung 4 zeigt einen Teil der Cache-Klasse.

Abbildung 4 die Cache-Klasse

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];
}
}
}

Wenn Sie die Interna der Cache-Klasse interessiert sind, finden Sie eine Version davon in mehrere Open-Source Softwareprojekte, einschließlich StructureMap, StoryTeller, FubuMVC, und ich glaube, Fluent NHibernate.

Das Muster zuordnen/reduzieren

Wie sich herausstellt, dass viele allgemeine Entwicklungsaufgaben mit funktionalen Programmiertechniken einfacher sind. Insbesondere auflisten und Set-Operationen im Code sind sehr viel einfacher mechanisch in Sprachen, die das Muster “ Zuordnung, reduzieren eingeben unterstützen. (In LINQ ist “ Zuordnung eingeben “ wählen eingeben und “ reduzieren eingeben “ Aggregat eingeben.) Überlegen Sie, wie Sie die Summe der ein Array von ganzen Zahlen berechnen möchten. In .NET 1.1 mussten Sie das Array etwa wie folgt durchlaufen:

Int [] Zahlen = new Int [] {1,2,3,4,5};
Int Summe = 0;
für (Int i = 0;i <Numbers.Length;i ++)
{
Summe Zahlen += [i];
}

Console.WriteLine(Sum);

Der Wave Spracherweiterungen zur Unterstützung von LINQ in .NET 3.5 bereitgestellt in funktionale Programmiersprachen allgemeine Funktionen Zuordnung/reduzieren. Heute könnte der oben stehende Code einfach als geschrieben werden:

Int [] Zahlen = new Int [] {1,2,3,4,5};
Int Summe = numbers.Aggregate ((x, y) = >X + y);

oder mehr einfach als:

Int Summe = numbers.Sum();
Console.WriteLine(Sum);

Fortsetzungen

Grob gesagt, ist eine Fortsetzung in der Programmierung eine Abstraktion der Art, die "was als Nächstes tun" kennzeichnetoder der "Rest der Berechnung." Manchmal ist es nützlich, um Teil eines Prozesses rechnerische zu einer anderen Zeitpunkt als in einer Anwendung Assistenten zu beenden, in dem ein Benutzer explizit ermöglichen die nächste Schritt oder kann den gesamten Prozess abzubrechen.

Wechseln Sie nun rechts in ein Codebeispiel. Genommen Sie an, Sie entwickeln eine Desktopanwendung in WinForms oder WPF. Häufig müssen Sie eine Art von lang andauernden Prozesses initiieren oder ein Bildschirm Aktion einen langsamen externen Dienst zugreifen. Aus Gründen der Benutzerfreundlichkeit möchten Sie sicherlich nicht den der Benutzeroberfläche Sperren und erleichtern nicht reagiert während der Service-Aufruf geschieht, sodass Sie in einem Hintergrundthread ausführen. Wenn schließlich der Service-Aufruf zurückkehrt, können Sie die Benutzeroberfläche mit den Daten vom Dienst zurückzukommen aktualisieren, aber wie jeder erfahrene WinForms oder WPF Entwickler weiß, Elemente der Benutzeroberfläche nur auf den wichtigsten Benutzeroberflächenthread aktualisiert werden können.

Sie können sicherlich die BackgroundWorker-Klasse, der in den System.ComponentModel-Namespace verwenden, jedoch einen anderen Ansatz auf Grundlage Lambda-Ausdrücke in ein CommandExecutor-Objekt, dargestellt durch diese Schnittstelle übergeben und Videoqualität:

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);
    }

Die erste Methode ist einfach eine Anweisung eine Aktivität in einem Hintergrundthread ausführen. Die zweite Methode, die in einem Func < Aktion > annimmtist noch interessanter. Sehen wir uns wie diese Methode i. d. r. Anwendung Code verwendet werden soll.

Zunächst wird davon gegangen Sie aus, dass Sie WinForms oder WPF Code mit dem Formular Supervising Controller des Model View Presenter-Musters strukturieren sind. (Siehe msdn.microsoft.com/magazine/cc188690.aspx für Weitere Informationen zu dem MVP-Muster). In diesem Modell soll die Presenter-Klasse ist zuständig für das Aufrufen einer Webdienstmethode langer und die Rückgabedaten verwenden, um die Ansicht zu aktualisieren. Die neue Presenter-Klasse wird einfach die ICommandExecutor--Schnittstelle dargestellt, behandeln alle threading und marshalling arbeiten, wie dargestellt in Abbildung 5 Thread früheren verwenden.

Abbildung 5 die Presenter-Klasse

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);
});
}
}

Die Presenter-Klasse ruft ICommandExecutor.Execute übergeben, in einem Block, der einem anderen Block zurückgibt. Der ursprüngliche Block Ruft den lang andauernde Service-Aufruf um einige Daten zu erhalten, und gibt einen Fortsetzung Block, der zum Aktualisieren der Benutzeroberfläche (IView in diesem Szenario) verwendet werden kann. In diesem speziellen Fall ist es wichtig, den Fortsetzung Ansatz verwenden, anstatt die IView nur zur gleichen Zeit zu aktualisieren, da das Update zurück an den Benutzeroberflächenthread gemarshallt werden.

Abbildung 6 zeigt die konkrete Implementierung der ICommandExecutor-Schnittstelle.

Abbildung 6 konkreten Implementierung von 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);
});
}
}

Die Methode ausführen (Func < Aktion >) aufruft Func < Aktion >in einem Hintergrundthread und nimmt werden die Fortsetzung (die Aktion von Func < Aktion > zurückgegeben) und verwendet eine SynchronizationContext die Fortsetzung in main Benutzeroberflächenthread ausführen Objekt.

Ich möchte Blöcke in der ICommandExecutor-Schnittstelle übergeben, wegen wie wenig ceremonial Code dauert die Hintergrundverarbeitung aufrufen. Bevor wir Lambda-Ausdrücke oder anonyme Delegaten in c#, musste ich in eine frühere Inkarnation dieser Ansatz eine ähnliche Implementierung, die wenig Command Muster-Klassen wie folgt verwendet:

public interface ICommand
    {
        ICommand Execute();
    }

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

Der Nachteil der erstere Ansatz ist, dass zusätzliche Command-Klassen nur, um den Hintergrundvorgang und den Code Ansicht aktualisieren modellieren schreiben musste. Die Funktionen zusätzliche Klasse Deklaration und Konstruktor sind ein wenig mehr Zeremonie Code, den wir mit den funktionalen Ansatz beseitigt werden kann, aber für mich noch wichtiger ist, dass der funktionale Ansatz mir diese eng miteinander verbundene Code in eine einzelne Stelle in den Presenter statt über die wenig Command-Klassen verteilt einfügen ermöglicht.

Fortsetzung übergeben Formatvorlage

Aufbauend auf Fortsetzung Konzept, können Sie die Fortsetzung übergeben Art der Programmierung Aufrufen eine Methode durch die Übergabe einer Fortsetzung anstelle der warten auf des von der Methode zurückgegebenen Wert verwenden. Die Methode akzeptiert die Fortsetzung ist zuständig entscheiden, ob und wann die Fortsetzung aufrufen.

In meinem aktuellen von MVC-Projekt sind Dutzende von Domänencontroller-Aktionen, die Updates aus Benutzereingaben, die vom Browser Clients über ein AJAX-Aufruf an ein Domänenobjekt Entität gesendet zu speichern. Die meisten dieser Controller-Aktionen aufrufen einfach unsere Klasse Repository zum Speichern der geänderten Entität, aber andere Aktionen verwenden andere Dienste, die Dauerhaftigkeit Arbeit durchzuführen. (Siehe meinen Artikel in der Ausgabe April von MSDN Magazinemsdn.microsoft.com/magazine/dd569757.aspx Weitere Informationen über die Repository-Klasse).

Der grundlegende Workflow diese Aktionen Controller entspricht:

  1. Überprüfen Sie die Domäne Entität und zeichnen Sie Validierungsfehler auf.
  2. Wenn Fehler vorhanden sind, senden Sie eine Antwort an den Client, dass der Vorgang fehlgeschlagen ist und schließen Sie die Überprüfungsfehler für die Anzeige auf dem Client ein.
  3. Wenn keine Gültigkeitsprüfungsfehler die Entität Domäne beibehalten und senden eine Antwort an den Client, der angibt, dass der Vorgang erfolgreich war.

Was wir tun möchten den grundlegenden Workflow zentralisieren ist aber weiterhin erlauben jedes einzelnen Controller Aktion variieren, wie die tatsächliche Dauerhaftigkeit durchgeführt wird. Heute ist mein Team dabei durch erben von einem CrudController < T >übergeordnete Klasse mit viel Vorlage-Methoden, die jede Unterklasse hinzufügen oder ändern das grundlegende Verhalten überschreiben kann. Diese Strategie auch zunächst ausgearbeitet, aber es ist schnell aufzuschlüsseln als Varianten erhöht haben. Mein Team ist nun mithilfe von Fortsetzung übergeben Formatvorlage Code, dass unser Controller Aktionen etwa die folgende Schnittstelle delegieren verschieben:

 

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

Eine typische Controller Aktion würde IPersistor durchführen des grundlegenden CRUD-Workflows und Angeben einer Fortsetzung, die IPersistor, verwendet um das Zielobjekt tatsächlich speichern informieren. Abbildung 7 zeigt ein Beispiel Domänencontroller Aktion, IPersistor aufgerufen, aber verwendet einen anderen Dienst als unsere grundlegende Repository für die tatsächliche Dauerhaftigkeit.

Abbildung 7 A Sample Controller Aktion

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));
}
}

Ich glaube, hier zu beachten ist es wichtig, dass IPersistor selbst Entscheidung, ob und wann die Fortsetzung von SolutionController bereitgestellten aufgerufen wird. Abbildung 8 wird eine Beispielimplementierung der IPersistor veranschaulicht.

Abbildung 8 eine Implementierung von 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));
}
}

Weniger Code schreiben

Gesagt habe ich ursprünglich in diesem Thema weil ich war interessiert Weitere Informationen zu funktionalen Programmierung und wie Sie sogar in c# oder Visual Basic angewendet werden kann. Im Verlauf des in diesem Artikel schreiben, haben ich gelernt, viel zu, wie nützlich funktionale Programmiertechniken in normale, alltägliche Aufgaben sein kann. Die wichtigsten Schlussfolgerung ich erreicht haben und was ich versucht haben, um hier zu vermitteln, die mit anderen Techniken, funktionalen Programmierung Ansätze verglichen wird häufig zum Schreiben von weniger Code und häufig weitere deklarativen Code für einige Vorgänge führen kann, und das ist fast immer eine gute Sache.

Jeremy Miller, ein Microsoft MVP für C#-ist auch der Autor des Tools open Source StructureMap (structuremap.sourceforge.net) für Abhängigkeitsinjektion mit .NET und das in Kürze StoryTeller (storyteller.tigris.org) Tool für FitNesse-Tests in .NET. Besuchen Sie seinen Blog The Shade Tree Developer unter codebetter.com/blogs/jeremy.miller Teil der CodeBetter-Website.