如何使用泛型创建Fluent界面

时间:2014-01-06 22:40:17

标签: c# generics expression fluent-interface

我想创建一个流畅的界面,可以这样使用:

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

我最终想要一个看起来像这样的集合:

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

我无法获得上述功能,但I did create something similar以类似的方式工作,但我并不特别喜欢如何设置流畅的界面。我宁愿让它读起来像我上面的方式。

2 个答案:

答案 0 :(得分:4)

有两种常用方法可以创建流畅的界面。

一种方法是添加到正在构建的类的当前实例,并从每个方法返回this

这样的事情:

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

这种构建器的问题在于您可以轻松编写错误代码:

var namesBuilder = new NamesBuilder();

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

如果我看到此代码,我希望namesBuilder1namesBuilder2每个只有一个名称,而namesBuilder则没有。但是,实现在所有三个变量中都有两个名称,因为它们是同一个实例。

实现流畅接口的更好方法是在延迟评估的构建器类上创建一个链,以便在构建完成后创建最终类。然后,如果你在构建过程中分支,你可能会犯错误。

以下是我希望编写的代码类型:

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

使这项工作的代码比“名称”示例稍微复杂一些。

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

此类构建器的另一个优点是您还可以维护强类型属性字段。

当然,您需要在Create方法中为您的映射添加正确的代码。

答案 1 :(得分:2)

您可以使用以下代码

来实现它
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;
    }
}

您不必指定所有先前设置的泛型类型,因为它们已被记住为返回类型的通用参数。可以跳过Properties方法调用的通用参数,因为它们将由编译器推断。与在任何地方使用object相比,你获得了更好的打字。

当然这是最简单的版本。您可以在这些类型之间传递更多信息,因为您指定了下一个必要类型的创建方式。

它还调用MapViewModel而不首先调用MapDomainModel(只要你创建构造函数internal并关闭所有单独的dll中的所有东西),这应该是一件好事。< / p>