有没有办法用“限制性 API”来实现构建器模式而不会太冗长

问题描述

在设计组件 X 时,我想使用允许指定 ABC 的构建器模式出于可用性目的,以流畅的方式显示值。

出于类似的可用性目的,我想使任何使用 X 的 API 的开发人员都无法(API 方面)指定 ABC 更多不止一次。

但是,由于 API 用户反馈的时间成本,我无法接受构建器的运行时失败,并且允许该值被第二次调用覆盖在功能上没有意义,因此也不能接受.

我为这个问题找到的唯一解决方案是创建 8 个(!)构建器接口,每个接口都有自己的此类构建器实现:

IHavenothing<X>
IHaveA<X>
IHaveB<X>
IHaveC<X>
IHaveAAndB<X>
IHaveAAndC<X>
IHaveBAndC<X>
IHaveAAndBAndC<X>

对于这样一个简单的构建器来说,这有点矫枉过正,但想不出比这更冗长的东西了。

是否有任何模式可以减轻这种构建器的冗长?

谢谢

解决方法

如果您强制对元素进行某种排序,则可以使用更少的接口。通过这样做,您不需要为所有 A+B、A+C 和 B+C 提供接口。

另一个技巧是只创建 1 个构建器实现类来实现所有接口,API 用户可能无法访问这些接口。

像这样:

interface ResultBuilder {
    static ABuilder builder() {
        return new ResultBuilderImpl();
    }

    interface ABuilder {
        BBuilder a(A a);
    }

    interface BBuilder {
        CBuilder b(B b);
    }

    interface CBuilder {
        ResultBuilder c(C c);
    }

    Result build();
}

和实现:

class ResultBuilderImpl implements ResultBuilder,ResultBuilder.ABuilder,ResultBuilder.BBuilder,ResultBuilder.CBuilder {
    private A a;
    private B b;
    private C c;

    public BBuilder a(A a) {
        this.a = a;
        return this;
    }

    public CBuilder b(B b) {
        this.b = b;
        return this;
    }

    public ResultBuilder c(C c) {
        this.c = c;
        return this;
    }

    public Result build() {
        return new Result(a,b,c);
    }

}

用法:

Result result = ResultBuilder.builder()
    .a(new A())
    .b(new B())
    .c(new C())
    .build();
,

Java 在解析类型时没有回溯(没有复杂的类型推断),但您可以构建一个构建器而不受限制以特定顺序设置值。

定义类型以在确定参数时进行注释:

interface A {}
interface B {}
interface C {}

定义类型以在参数未确定时进行注释:

interface NA {}
interface NB {}
interface NC {}

构建器看起来像:

static class Builder<R,S,T> {
    int _a = 0;
    int _b = 0;
    int _c = 0;
    R r;
    S s;
    T t;
    Builder(int _a,int _b,int _c,R r,S s,T t) {
        this._a = _a;
        this._b = _b;
        this._c = _c;
        this.r = r;
        this.s = s;
        this.t = t;
    }
    static <R extends NA,T> Builder<A,T> A(Builder<R,T> z,int n) {
        return new Builder<>(n,z._b,z._c,null,null);
    }
    static <R,S extends NB,T> Builder<R,B,T> B(Builder<R,int n) {
        return new Builder<>(z._a,n,T extends NC> Builder<R,C> C(Builder<R,null);
    }
    static Builder<NA,NB,NC> builder() {
        return new Builder<>(0,null);
    }
    <M,N> M set(BiFunction<Builder<? extends R,? extends S,? extends T>,N,M> k,N n) {
        return k.apply(this,n);
    }
    void build() {
        System.out.printf("<%d,%d,%d>%n",_a,_b,_c);
    }
}

什么时候:

  1. _a,_b,... 是每个输入参数。
  2. r,s,... 是在确定或未确定时执行的类型注释。
  3. 构造函数。
  4. A,B,... 是将非固定注释转换为固定注释的静态限制。
  5. builder 构造未固定的初始值。
  6. set 发挥作用,在类型注释中启用回溯。
  7. build 在这种情况下打印值。

现在我们可以按任意顺序设置值

builder().set(Builder::B,2).set(Builder::A,1).build();
builder().set(Builder::A,1).set(Builder::C,3).set(Builder::B,2).build();

带输出

<1,2,0>
<1,3>

但是如果你尝试设置两次

enter image description here

,

好吧,您可以每一步实现一个类或接口,并允许在每一步创建 X。像这样:

class X {  }

interface BuildStep {
    X build();          
}

interface StepC extends BuildStep {
    BuildStep c(String param);
}

interface StepB extends StepC {
    StepC b(String param);
}

interface StepA extends StepB {
    StepB a(String param);
}

class XBuilder implements StepA {

    @Override
    public StepC b(String param) {  
        //handle param
        return this;
    }

    @Override
    public BuildStep c(String param) {
        //handle param
        return this;
    }

    @Override
    public StepB a(String param) {
        //handle param
        return this;
    }

    @Override
    public X build() {
        //build the new X
        return new X();
    }
    
    static XBuilder builder() {
        return new XBuilder ();
    }
}   

这样你的案例就会看起来像这样(将强制执行调用顺序,即 A -> B -> C -> X,同时能够在该链中的任何位置开始):

X x;

//IHaveNothing<X>
x = XBuilder.builder().build();

//IHaveA<X>
x = XBuilder.builder().a(param).build();

//IHaveB<X>
x = XBuilder.builder().b(param).build();

//IHaveC<X>
x = XBuilder.builder().c(param).build();

//IHaveAAndB<X>
x = XBuilder.builder().a(param).b(param).build();

//IHaveAAndC<X>
x = XBuilder.builder().a(param).c(param).build();

//IHaveBAndC<X>
x = XBuilder.builder().b(param).c(param).build();

//IHaveAAndBAndC<X>
x = XBuilder.builder().a(param).b(param).c(param).build();

但是,这是不允许的:

//those make the compiler cry and slap you in the face :)
x = XBuilder.builder().c(param).a(param).b(param).build();
x = XBuilder.builder().a(param).a(param).b(param).build();
,

IHaveNothing 仅包含 ProvideA(...)

仅 IHaveA 和 ProvideB(...)

IHaveAB 仅包含 ProvideC(...)

仅带有 Build() 的 IHaveABC

应该足够了还是我没有完全理解您的要求?不过,这种设计会强制执行命令。