我想创建一个流畅的界面,可以像这样使用:
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;}
}
我无法完成上述任务,但我确实创建了类似的东西,它以类似的方式工作,但我不太喜欢如何设置流畅的界面。我宁愿让它像上面那样读。
创建流畅界面有两种常见方法。
一种方法是添加到正在构建的类的当前实例,并从每个方法返回
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
方法中输入正确的映射代码。
可以通过以下代码实现
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 中的所有内容),这应该是一件好事。