2014-01-06 16 views
5

erstellen Ich wollte eine Fluent Interface erstellen, die wie so verwendet werden können:Wie ein Fluent Interface mit Generics

void Main() { 
    ModelStateMappings.MapDomainModel<Book>().MapViewModel<BookViewModel>() 
     .Properties(book => book.Author, vm => vm.AuthorsName) 
     .Properties(book => book.Price, vm => vm.BookPrice); 

    ModelStateMappings.MapDomainModel<Store>().MapViewModel<StoreViewModel>() 
     .Properties(store => store.Owner, vm => vm.OwnersName) 
     .Properties(store => store.Location, vm => vm.Location); 
} 

Ich wollte mit einer Sammlung am Ende, die ungefähr so ​​aussah:

static class ModelStateaMappings { 
    private static IList<ModelMappings> mappings; 
    // other methods in here to get it working 
} 

class ModelMappings { 
    public Type DomainModelType {get;set;} 
    public Type ViewModelType {get;set;} 
    public IList<PropertyMapping> PropertyMappings {get;set;} 
} 

class PropertyMapping { 
    public Expression<Func<object, object>> DomainProperty {get;set;} 
    public Expression<Func<object, object>> ViewModelProperty {get;set;} 
} 

Ich war nicht in der Lage, die oben genannten erreicht, aber I did create something similar, die in ähnlicher Weise funktioniert, aber ich mag nicht besonders, wie ich die fließenden Schnittstellen einrichten musste. Ich hätte es lieber so gelesen, wie ich es oben habe.

+0

Wie sieht die Benutzeroberfläche aus, die Sie jetzt haben? Was war das spezifische Problem, das Sie davon abgehalten hat, es so aussehen zu lassen, wie Sie es wollten? –

+0

Ich konnte nicht herausfinden, wie man die generische Inferenz vollständig auf die .Properties() -Methode herunterzieht, ohne beide Typen gleichzeitig zu übergeben. Die Art, wie ich das machen möchte, besteht darin, jedes generische Element einzeln zu übergeben und dann in der Property() -Methode zusammenzuführen. –

+0

Klingt so, als ob Sie ein Array von Typen benötigen. Hast du einen Blick auf [Automapper?] Geworfen (https://github.com/AutoMapper/AutoMapper) –

Antwort

2

Sie können es erreichen mit folgenden Code

static class ModelStateMappings 
{ 
    public static DomainModelMapping<TDomainModel> MapDomainModel<TDomainModel>() 
    { 
     // edit the constructor to pass more information here if needed. 
     return new DomainModelMapping<TDomainModel>(); 
    } 
} 

public class DomainModelMapping<TDomainModel> 
{ 
    public ViewModelMapping<TDomainModel, TViewModel> MapViewModel<TViewModel>() 
    { 
     // edit the constructor to pass more information here if needed. 
     return new ViewModelMapping<TDomainModel, TViewModel>(); 
    } 
} 

public class ViewModelMapping<TDomainModel, TViewModel> 
{ 
    public ViewModelMapping<TDomainModel, TViewModel> 
     Properties<TDomainPropertyType, TViewModelPropertyType>(
      Expression<Func<TDomainModel, TDomainPropertyType>> domainExpr, 
      Expression<Func<TViewModel, TViewModelPropertyType>> viewModelExpr) 
    { 
     // map here 
     return this; 
    } 
} 

Sie müssen generische Typen nicht angeben alle zuvor festgelegt, da sie bereits als generische Parameter zurück Typ erinnert werden. Generische Parameter für den Methodenaufruf Properties können übersprungen werden, da sie vom Compiler abgeleitet werden. Und Sie können besser tippen als mit object s überall.

Natürlich ist das die einfachste Version. Sie können viel mehr Informationen zwischen diesen Typen weitergeben, da Sie angeben, wie der nächste erforderliche Typ erstellt wird.

Es macht auch MapViewModel ohne Aufruf MapDomainModel zunächst unmöglich (sobald Sie die Konstrukteure internal und schließt alles in separaten DLL machen) nennen, was eine gute Sache sein sollte.

4

Es gibt zwei gebräuchliche Möglichkeiten, um eine flüssige Schnittstelle zu erstellen.

Eine Möglichkeit besteht darin, der aktuellen Instanz der Klasse, die gebaut wird, hinzuzufügen und this von jeder Methode zurückzugeben.

Etwas wie folgt aus:

public class NamesBuilder 
{ 
    private List<string> _names = new List<string>(); 
    public NamesBuilder AddName(string name) 
    { 
     _names.Add(name); 
     return this; 
    } 
} 

Das Problem mit dieser Art von Builder ist, dass Sie fehlerhaftem Code einfach schreiben:

var namesBuilder = new NamesBuilder(); 

var namesBuilder1 = namesBuilder.AddName("John"); 
var namesBuilder2 = namesBuilder.AddName("Jack"); 

Wenn ich diesen Code sah ich erwarten würde, dass namesBuilder1 und namesBuilder2 würde jeder nur einen Namen haben, und dass namesBuilder keine haben würde. Die Implementierung würde jedoch beide Namen in allen drei Variablen haben, da sie dieselbe Instanz sind.

Der bessere Weg zur Implementierung einer fließenden Oberfläche besteht darin, eine Kette von Builder-Klassen zu erstellen, die langsam ausgewertet werden, sodass Sie die letzte Klasse erstellen können, sobald Sie fertig sind. Wenn Sie dann mitten in den Bauprozess verzweigen, können Sie einen Fehler machen.

Hier ist die Art von Code zu schreiben, ich erwarten würde:

var bookMap = 
    ModelStateMappings 
     .Build<Book, BookViewModel>() 
     .AddProperty(book => book.Author, vm => vm.AuthorsName) 
     .AddProperty(book => book.Price, vm => vm.BookPrice) 
     .Create(); 

var bookStore = 
    ModelStateMappings 
     .Build<Store, StoreViewModel>() 
     .AddProperty(store => store.Owner, vm => vm.OwnersName) 
     .AddProperty(store => store.Location, vm => vm.Location) 
     .Create(); 

Der Code, diese Arbeit zu machen, ist ein wenig komplizierter als die „Namen“ Beispiel.

public static class ModelStateMappings 
{ 
    public static Builder<M, VM> Build<M, VM>() 
    { 
     return new Builder<M, VM>(); 
    } 

    public class Builder<M, VM> 
    { 
     public Builder() { } 

     public Builder<M, VM> AddProperty<T>(
      Expression<Func<M, T>> domainMap, 
      Expression<Func<VM, T>> viewModelMap) 
     { 
      return new BuilderProperty<M, VM, T>(this, domainMap, viewModelMap); 
     } 

     public virtual Map Create() 
     { 
      return new Map(); 
     } 
    } 

    public class BuilderProperty<M, VM, T> : Builder<M, VM> 
    { 
     private Builder<M, VM> _previousBuilder; 
     private Expression<Func<M, T>> _domainMap; 
     private Expression<Func<VM, T>> _viewModelMap; 

     public BuilderProperty(
      Builder<M, VM> previousBuilder, 
      Expression<Func<M, T>> domainMap, 
      Expression<Func<VM, T>> viewModelMap) 
     { 
      _previousBuilder = previousBuilder; 
      _domainMap = domainMap; 
      _viewModelMap = viewModelMap; 
     } 

     public override Map Create() 
     { 
      var map = _previousBuilder.Create(); 
      /* code to add current map to Map class */ 
      return map; 
     } 
    } 
} 

Der andere Vorteil dieses Builder-Typs besteht darin, dass Sie auch stark typisierte Eigenschaftsfelder verwalten.

Natürlich müssen Sie den richtigen Code für Ihr Mapping in der Create Methode eingeben.