我正在尝试实现一个复杂的构建器来帮助我的测试环境。为此,我将代码重构为具有一个方法:
public TestContext Add<T>(Action<IBuilder<T>> configurator) where T : class, new()
{
IBuilder<T> builder = CreateBuilderOf<T>();
configurator(builder);
T item = builder.GetItem();
RepositoryOf<T>().Insert(item);
SetCurrent(item);
return this;
}
当我需要在调用方法时指定配置时,就会出现问题:
TestContext.Instance.Add<Person>(personBuilder => ((PersonBuilder)personBuilder).Name("SMITH"));
我需要能够在配置器中使用特定于类型的方法,这些方法由具体的构建器实现,例如:
public PersonBuilder : IBuilder<Person>
{
private Person Item;
public PersonBuilder() { Item = new Person(); }
public Name(string mame) { Item.Name = name; }
public Person GetItem() { return Item; }
}
显然,即使
Action<PersonBuilder>
实现了Action<IBuilder<Person>>
,因此也不允许将PersonBuilder
作为IBuilder<Person>
传递,因此强制转换。我非常愿意:
不需要在lambda内部投放,而是在开始时投放,例如
(PersonBuilder personBuilder) => personBuilder.Name("SMITH")
,但是归结为Action<PersonBuilder>
的实例,因此同样无效;在Add的参数中使用诸如
BuildSimplePerson(PersonBuilder builder)
之类的函数:Add<Person>(BuildSimplePerson)
我想我可以通过两个
BuildSimplePerson
实现来进行类型转换:private void BuildSimplePerson(IBuilder<Person> builder)
{
BuildSimplePerson(builder as PersonBuilder);
}
private void BuildSimplePerson(PersonBuilder builder)
{
builder.Name("SMITH");
}
但这并不是一个很好的解决方案。
我还意识到将
Action<PersonBuilder>
传递为Action<IBuilder<Person>>
是不正确的,因为我们不知道该函数的参数是否确实是PersonBuilder
或IBuilder<Person>
的任何其他实现。我该如何做得更好?
最佳答案
正如我的评论已经指出的那样,问题在于您当前的代码假定CreateBuilderOf<T>
返回一个PersonBuilder
,但实际上它可以返回实现IBuilder<Person>
的任何内容,在这种情况下,您的转换将失败。
您的代码看起来像是通用的,但实际上并非如此。您始终想在具体的类(PersonBuilder
)上工作,而不是在常规接口IBuilder<Person>
上工作。
我的理解是,您希望一个通用的Add<T>
方法避免为每种类型在其内部重复该代码。
这是我的方法:
public TestContext Add<T>(IBuilder<T> builder) where T : class, new()
{
T item = builder.GetItem();
RepositoryOf<T>().Insert(item);
SetCurrent(item);
return this;
}
您可以这样称呼它:
TestContext.Instance.Add<Person>(CreatePersonBuilder().Name("SMITH"));
显然,您需要为每种要添加的类型都有一个
CreateXBuilder
方法。但是,我认为您至少已经隐含了这一点,因为无论如何我都认为您的CreateBuilderOf<T>
方法是一个巨大的switch语句。如果您不想创建此类方法,那么获取构建器的另一种方法将是通用方法,如下所示:
CreateBuilder<PersonBuilder>()
但是实际上,这实际上只是一个
new PersonBuilder()
,因此您实际上可以简单地使用TestContext.Instance.Add<Person>(new PersonBuilder().Name("SMITH"));
Configure
方法将非常相似:TestContext.Instance.Configure<Person>(id, p => new PersonBuilder(p).Name("SMITH"));
这将传递ID,
Configure
方法将使用该ID查找该对象,该对象又传递给回调。因此,Configure
的第二个参数不是Action<IBuilder<T>>
而是Action<T>
。与现有代码相比,此方法还有另一个优势:
您现有的代码不仅假定
PersonBuilder
将是IBuilder<Person>
的实现。不,您的代码还假定它具有一个没有参数的构造函数,而该构造函数采用一个Person
。这些假设是编译器无法验证的。使用上面显示的代码,构建器实现可以毫无问题地使用其他参数,并且编译器将验证一切正常。
关于c# - 如何在Action <IBuilder <T >>中使用泛型来处理特定于类型的配置?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/35896113/