【问题标题】:Common Builder base instance for a class hierarchy using Lombok使用 Lombok 的类层次结构的 Common Builder 基实例
【发布时间】:2019-12-19 04:53:41
【问题描述】:

我有一个包含一个抽象基类和几个子类的类层次结构。基类有大约 25 个字段,每个子类有一个额外的 0-8 个字段。

我想使用 Builder 模式来构造每个子实例,并且我想尽可能地使用 Lombok 来保持代码简洁。在this suggestion 之后,我的代码如下:

@AllArgsConstructor
@Data
public abstract class Base {
   private int b1, b2, ... , b25;
}

public class C1 extends Base {
   private int c11, c12, ... , c16;

   @Builder
   private C1(int b1, int b2, ..., int b25, int c11, ... int c16) {
       super(b1, b2, ...., b25);
       this.c11 = c11;
       ...
       this.c16 = c16;
   }
}

public class C2 extends Base {

   @Builder
   private C2(int b1, int b2, ..., int b25) {
       super(b1, b2, ...., b25);
   }
}

这使得构建子类变得容易

C1 c1 = C1.builder().b1(1).b2(2)....b25(25).c11(101).c12(102).build();
C2 c2 = C2.builder().b1(1).b2(2)....b25(25).build();

问题是每次创建任何子类时都会重复 .b1().b2()... 链式调用。

理想情况下,无论正在构建哪个子类,我都想要一种设置 B 值的通用方法。 (假设有另一个名为 BValuesProvider 的类可以提供这些值)

public void setBValues(BValuesProvider bv, // what else goes here??? //) {
    // something.b1(bv.b1()).b2(bv.b2()) ...
}

public createC1(BValuesProvider bv, c11, c12, ..., c16) {
    C1.Builder c1b = C1.builder().c11(c11).c12(c12)....c16(c16);
    // Call setBValues somehow
    return c1b.build();
}

public createC2(BValuesProvider bv) {
    // Call setBValues somehow
    return c2b.build();
}

我当前的解决方案是将 @Data 注释附加到基类以公开 setter/getter,因此我的代码如下所示:

public void setBValues(BValuesProvider bv, Base cx) {
    cx.setB1(bv.b1());
    cx.setB2(bv.b2());
    ...
    cx.setB25(bv.b25());
}

public createC1(BValuesProvider bv, c11, c12, ..., c16) {
    C1 c1 = C1.builder().c11(c11).c12(c12)....c16(c16).build();
    setBValues(bv, c1);
    return c1;
}

public createC2(BValuesProvider bv) {
    C2 c2 = C2.builder().build();
    setBValues(bv, c2);
    return c2;
}

问题:

  • 有没有更好的方法来做到这一点?具体来说,我觉得首先(完全)构建一个子类,然后在其上调用setBxx() 函数似乎是一种不好的模式。暴露 setter 本身会使类变得非常可变。

  • 还有其他关于构建器/继承的问题

    但是,他们都没有谈论让每个孩子都有一个“基础建设者” builder 是一个子类。所以,我无法弄清楚使用泛型, setBValues 函数的第二个参数应该是什么。

  • 我还尝试了 Lombok 的 @Superbuilder 注释,但同样,虽然它大大简化了代码,但我仍然不知道如何获得基础构建器。

【问题讨论】:

  • 如果你使用@SuperBuilder,你不能简单地创建一个方法(可能在你的BValuesProvider类中)以抽象BaseBuilder作为参数,并在其中调用builder的setter吗?
  • 不确定我是否理解如何做到这一点 - 你能给我看一个小例子吗?另外,我应该提到BValuesProvider 不是我必须修改的类,因为它属于不同的包。我想我可以为此添加另一个接口类(?)。

标签: java builder lombok


【解决方案1】:

这可以使用(实验性)@SuperBuilder 注释和 lombok >= 1.18.4 来实现。您可以自定义 Base@SuperBuilder,方法是添加一个将 BValuesProvider 作为参数并从中设置所有值的方法:

@SuperBuilder
public abstract class Base {
    public static abstract class BaseBuilder<C extends Base, B extends BaseBuilder<C, B>> {
        public B fillFromProvider(BValuesProvider bv) {
            b1(bv.b1());
            b2(bv.b2());
            ...
            return self();
        }
    }
    ...
}

然后你可以这样使用它(bv 是一个BValuesProvider 实例):

C1 c1 = C1.builder().fillFromProvider(bv).c11(11).build();

【讨论】:

  • 我认为这会起作用,谢谢!我唯一担心的是我的代码是一个被其他包使用的库。如果我采用这种方法,我的库将需要导入(并注意)诸如 BValuesProvider 之类的东西,它们是属于其他包的类(并且与我的库完全无关)。
  • 好吧,您可以将fillFromProvider 方法从构建器移到其他类。但是,构建器必须知道提供者,或者其他类(例如提供者)必须知道构建器。我看不出有什么办法。
【解决方案2】:

在这里。虽然这不是我提出的确切问题的答案,但我想分享一个仍然使用 Lombok 但不使用 @Builder 的替代方案。

@Getter
public abstract class Base<T extends Base> {
   private int b1, b2, ... , b25;
   private T cast() { return (T) this; }
   public T setB1(final int b1) { this.b1 = b1; return cast(); }
   public T setB2(final int b2) { this.b2 = b2; return cast(); }
   ...
}

@Getter @Setter @Accessors(chain = true)
public class C1 extends Base<C1> {
   private int c11, c12, ... , c16;
   public static C1 init() { return new C1(); }
   private C1() {}
}

@Getter @Setter @Accessors(chain = true)
public class C2 extends Base<C2> {
   public static C2 init() { return new C2(); }
   private C2() {}
}

我刚刚生成了基类并在子类上使用了链式访问器。然后调用者将被修改为:

public void setBValues(BValuesProvider bv, Base cx) {
    cx.setB1(bv.b1())
      .setB2(bv.b2())
      ...
      .setB25(bv.b25());
}

public createC1(BValuesProvider bv, c11, c12, ..., c16) {
    C1 c1 = C1.init().setC11(c11)....setC16(c16);
    setBValues(bv, c1);
    return c1;
}

public createC2(BValuesProvider bv) {
    C2 c2 = C2.init();
    setBValues(bv, c2);
    return c2;
}

优点:

  • 我的代码需要不知道BValuesProvider
  • 以链式方式设置公共基础参数对我来说似乎更自然

缺点:

  • 基类有点冗长,但总的来说,它并没有那么糟糕,因为我们在每个子类中都避免了那个大的构造函数。
  • 这并不是真正的 Builder 模式,因为我们正在创建对象并对其执行一系列 set(),而不是对其执行 .builder().x(x).y(y).build()

【讨论】:

    猜你喜欢
    • 2011-12-20
    • 2016-04-12
    • 2018-10-18
    • 1970-01-01
    • 2020-02-26
    • 1970-01-01
    • 2011-05-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多