流利的生成器具有继承的专业化和专业化的方法返回好奇地重复出现的模板

问题描述

我正在尝试编写其他开发人员可以流畅使用的数据管道构建器。如果从预期结果的示例开始,这可能是最简单的:

var b = new Builder();
b.Batch().BatchedOperation().Unbatch().UnbatchedOperation().etc.

我已经能够创建一个抽象生成器,该生成器可以使用奇怪的重复模板模式来进行特殊化。

我苦苦挣扎的要点是,某些操作只能在某些其他操作之后才能进行。具体来说,管道中有两种主要类型的操作:对实例集进行操作的操作以及对单个实例进行操作的操作。因此,仅在BatchedOperation的管道上执行Batched,而在UnbatchedOperation的管道上执行Unbatched

为了下面的示例代码,我将管道随时视为两种形式之一:Foo形式或Bar形式。这基本上等同于批处理或未批处理,但是它可以将代码缩减到核心问题,而无需弄清楚批处理或未批处理的确切含义,并且消除了混乱。

首先,假设我从基本的CRTP开始,如下所示:

public abstract class Builder<TBuilder> where TBuilder : Builder<TBuilder>
{
    protected TBuilder builder;
    internal Builder() => builder = (TBuilder)this;
    public TBuilder Bar() => builder;
    public TBuilder Foo() => builder;
}

我可以为该构建器创建一些特殊化的东西,

public class SpecialBuilder : Builder<SpecialBuilder>
{
    public SpecialBuilder() : base()    { }
    public SpecialBuilder Special() => builder;
}

但是,这样做的问题是它允许我做类似的事情:

var b = new SpecialBuilder();
b.Foo().Foo().etc

这是不好的,因为一旦Foo()终止了管道,就应该不可能再次Foo(),因为它现在处于Bar()可用状态。需要明确的是,这将引发运行时错误,但在编译时(或特别是在智能感知中)并没有捕获到该错误

我可以使用接口来限制管道操作的结果:

public interface IBar<T> { IFoo<T> Bar(); }
public interface IFoo<T> { IBar<T> Foo(); }

public abstract class Builder<TBuilder> : IFoo<TBuilder>,IBar<TBuilder>
where TBuilder : Builder<TBuilder>
{
    protected TBuilder builder;
    internal Builder() => builder = (TBuilder)this;
    public IFoo<TBuilder> Bar() => builder;
    public IBar<TBuilder> Foo() => builder;
}

但是,这又回到了不可继承的构建器上,因为现在Foo() d之后,我的派生构建器便无法运行。那时,它不再Special() d,因为它现在是IBar,而不是SpecialBuilder

似乎我需要其他专用接口:

public interface ISpecialFoo<T> : IFoo<T> { T Special(); }
public interface ISpecialBar<T> : IBar<T> { T Special(); }

但是,当然,抽象的Builder仍不能指定Bar()返回IFoo<TBuilder>,因为它仍然不是SpecialBuilder,因此不能Special() d。因此,似乎接口返回类型本身 也需要遵循奇怪的重复模板模式。

这是我的大脑开始受伤的地方。我想也许是这样:

public abstract class Builder<TBuilder,TFoo,TBar> 
    : IFoo<TBuilder>,IBar<TBuilder> // errors for both of these
    where TBuilder : Builder<TBuilder,TBar>,IFoo<TBuilder>,IBar<TBuilder>,TBar
    where TFoo : IFoo<TBuilder>
    where TBar : IBar<TBuilder>
{
    protected TBuilder builder;
    internal Builder() => builder = (TBuilder)this;
    public TFoo Bar() => builder;
    public TBar Foo() => builder;
}

但这给了我两个尝试从上述接口继承的对称错误

'Builder '未实现接口成员 IFoo.Foo()'。 'Builder .Foo()'无法 实现“ IFoo.Foo()”,因为它没有 匹配的“ IBar”返回类型

'Builder '未实现接口成员 'IBar.Bar()'。 'Builder .Bar()'无法 实现“ IBar.Bar()”,因为它没有 匹配的返回类型“ IFoo”。

这甚至有可能做到吗?我真的很想为该代码用户提供编译时的帮助,例如,Intellisense仅显示构建器状态的有效操作。但是他们的收获显然是我在这里的痛苦。

这是一个完整的控制台应用程序,可以演示我得到的最接近的应用程序:

public interface ISpecial { SpecialBuilder Special(); }
public interface ISpecialFoo : IFoo<SpecialBuilder>,ISpecial { }
public interface ISpecialBar : IBar<SpecialBuilder>,ISpecial { }
public interface IBar<T> { IFoo<T> Bar(); }
public interface IFoo<T> { IBar<T> Foo(); }

internal class Program
{
    private static void Main(string[] args)
    {
        var b = new SpecialBuilder();
        b.Special().Foo().Special().Bar();
    }
}

public abstract class Builder<TBuilder,TBar>
    where TBuilder : Builder<TBuilder,TBar
    where TFoo : IFoo<TBuilder>
    where TBar : IBar<TBuilder>
{
    protected TBuilder builder;
    internal Builder() => builder = (TBuilder)this;
    public TFoo Bar() => builder;
    public TBar Foo() => builder;
}

public class SpecialBuilder : Builder<SpecialBuilder,ISpecialFoo,ISpecialBar>,ISpecialBar
{
    public SpecialBuilder() : base() { }
    public SpecialBuilder Special() => builder;
}

解决方法

可以通过简单地将类型信息挂在各处来实现。

// I moved the generic interfaces inside the CRTP base
// to save some typing for the CRTP consumers.
// You can also move IState1 and IState2 outside,and
// they will need generic parameters TIState1,TIState2.
public abstract class Builder<TBuilder,TIState1,TIState2>
  : Builder<TBuilder,TIState2>.IState1,Builder<TBuilder,TIState2>.IState2
  // This constraint surprisingly (or not) compiles.
  where TBuilder : Builder<TBuilder,TIState2>,TIState2
  where TIState1 : Builder<TBuilder,TIState2>.IState1
  where TIState2 : Builder<TBuilder,TIState2>.IState2
{
  public interface IState1 { TIState2 OpState1(); }
  public interface IState2 { TIState1 OpState2(); }

  private TBuilder that;
  protected Builder() { that = (TBuilder)this; }

  public TIState2 OpState1() { return that; }
  public TIState1 OpState2() { return that; }
}

public sealed class MyBuilder
  : Builder<MyBuilder,MyBuilder.IMyState1,MyBuilder.IMyState2>,MyBuilder.IMyState2
{
  public interface IMySpecial { MyBuilder OpSpecial(); }
  public interface IMyState1 : IState1,IMySpecial { }
  public interface IMyState2 : IState2,IMySpecial { }

  public MyBuilder OpSpecial() { return this; }
}

现在,如果您尝试使用new MyBuilder().OpState1().,由于返回类型为IMyState2,您将仅看到OpSpecialOpState2object方法。

但是,因为接口的分配非常慢,所以使用接口来限制方法可见性的方法对性能有严重的影响。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...