如何使用泛型创建流畅的界面

问题描述 投票:0回答:2

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

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

我无法完成上述任务,但我确实创建了类似的东西,它以类似的方式工作,但我不太喜欢如何设置流畅的界面。我宁愿让它像上面那样读。

c# generics expression fluent-interface
2个回答
5
投票

创建流畅界面有两种常见方法。

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

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

如果我看到这段代码,我会期望

namesBuilder1
namesBuilder2
各只有一个名称,而
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
方法中输入正确的映射代码。


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 中的所有内容),这应该是一件好事。

© www.soinside.com 2019 - 2024. All rights reserved.