如何使用泛型来处理 Action<IBuilder> 中特定于类型的配置<T>?



我正在尝试实现一个复杂的构建器来帮助我的测试上下文。为此,我重构了我的代码以拥有一个方法:

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 内部进行转换,而是在 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的构造函数。编译器无法验证这些假设。
使用我上面显示的代码,构建器实现可以毫无问题地获取其他参数,编译器将验证一切正常。

最新更新