【问题标题】:Java abstract Step Builder patternJava 抽象 Step Builder 模式
【发布时间】:2021-11-16 23:00:08
【问题描述】:

我正在设计一个 java 系统,用户可以在其中以流畅的风格定义一些规则。

规则有许多属性,它们部分是相互排斥的。 我们使用带有验证的构建器模式。

为了使系统更易于使用,我们想引入一个 StepBuilder,以引导用户完成所有必要的步骤。

有不同类型的规则,但都有一些共同的属性。

当前系统:

abstract BaseBuilder<T extends BaseBuilder<T>> {

   protected String property1;
   protected String property2;
   protected String property3;

   abstract Rule build();

   public T withProperty1(String data) { 
      this.property1 = data; 
      return this; 
   }

   public T withProperty2(String data) { 
       this.property2 = data; 
       return this; 
   }

   public T withProperty3(String data) { 
       //this should only be possible if property2 is not set or similar logic
       this.property3 = data; 
       return this; 
   }
}

//there are a few others like this e.g. SpecialBuilder1-10
class SpecialRuleBuilder extends BaseBuilder<SpecialBuilder> {

       protected String special1;
       protected String special2;

       public T withSpecial1(String data) { 
          this.special1 = data; 
          return this; 
       }

       public T withSpecial2(String data) { 
           this.special2 = data; 
           return this; 
       }

     @Override
     Rule builder() {
        return new SpecialRule(property1, property3, special1, special2, ....);
     }

     static SpecialRuleBuilder builder() {
        return new SpecialRuleBuilder();
     }
    
}

class BuilderTest() {
   
   //User can set anything, no steps are enforced at compile time
   Result result = SpecialBuilder.builder()
                        .property1("some")
                        .special2("thing")
                        .build(); 

}

我们如何使用包含层次结构(父类)的 StepBuilder,以便用户不能两次进入同一步骤 不能意外返回到上一个基本步骤并再次设置一些互斥属性.

理想情况下,用户不应该知道所有特殊的构建器,并且具有相同的入口点和引导步骤。例如:

Result result = GeneralBuilder.builder()
                              .withBaseProperty1("asdas") <-- Step 1
                              .withBaseProperty2("asd")  <-- Step 2, Step 3 is now not visible, continue with all possible special options
                              .withSpecial1("asd") <-- no we are in the step of one concrete builder, and should not get "out" again, not even to the base methods

我知道如何定义接口步骤,我只是不知道如何在基本步骤的末尾包含特殊构建器的所有可能的开始步骤,因为更高的接口/类可能不应该依赖于较低的部分层次结构。

这有可能吗?

【问题讨论】:

  • “所以用户不能两次进入同一个步骤”您要避免的问题是什么?
  • 应该逐步指导用户,应该没有(或很少)可能建立错误的规则 - 因为这只会在运行时出现。相同的步骤实际上不会有问题,但如果我在 SpecialBuilderStep 中并且可以再次调用基类的所有方法,我可以例如再次设置 base property3,即使我之前设置了 base property2 并且它们是互斥的。规则可以变得非常大,并且有数千条,因此它应该尽可能简单/安全。
  • 我同意this answer 的第一句话:“这是个坏主意;您需要编写的代码数量之多令人震惊。”。我使用Error Prone 解决了一个类似的问题,使同一个setter 的多次调用成为编译器错误。因此,您可以编写代码,调用您喜欢的任何设置器,但它会在编译时失败。这不是一个“纯 Java”解决方案,但它比您尝试的要容易得多。

标签: java generics design-patterns interface builder-pattern


【解决方案1】:

这是个坏主意;您需要编写的代码数量之多令人瞠目结舌。此外,虽然 IDE 中引导式“自动完成”体验的吸引力是显而易见的,但要意识到有时构建器以更流畅(呵呵)的方式使用:一些代码创建一个构建器,设置一半的东西,然后返回 builder 以便其他代码可以从中断的地方继续。或者,一个将构建器作为参数的实用方法,并在其上设置一些东西。当你走这条路时,这些用例变得尴尬甚至不可能。因此,您花了 时间编写此代码,但从 API 角度来看,这甚至不是一个干净利落的胜利!

如果你想无视我的建议并照办,好吧,好吧。你的葬礼。一个被警告过的人算两个,等等。它看起来像这样:

public interface BridgeBuilder1 {
    public BridgeBuilder2 name(String name); // mandatory
}

public interface BridgeBuilder2 {
    public BridgeBuilder3 buildYear(int year);  // mandatory
}

public interface BridgeBuilder3 {
    // one of these two is mandatory, and they are mutually exclusive.
    public BridgeBuilder4 lanes(int lanes);
    public BridgeBuilder4 width(int widthInM);
}

public interface BridgeBuilder4 {
    // optional stuff, and the build itself.
    public BridgeBuilder4 color(Color c);
    public BridgeBuilder4 country(String name);
    public Bridge build();
}

// intentionally private class! Should be an inner class
// of your public Bridge class.
private static class BridgeBuilder implements BridgeBuilder1, BridgeBuilder2, BridgeBuilder3, BridgeBuilder4 {
    private String name;

    public BridgeBuilder2 name(String name) {
        this.name = name;
        return this;
    }

    // the fluent, chaining 'setters' for _ALL_ properties here.
    // Match the return types exactly to how they
    // are defined in the interfaces.

    public Bridge build() {
        return new Bridge(name, year, ....);
    }
}

...

public class Bridge {
    Bridge(String name, ...) {
       // a package private constructor.
    }

    public static BridgeBuilder1 builder() {
        return new BridgeBuilder();
    }
}

这应该足以突出它的工作原理。

但不要。

注意:真正好的解决方案是将构建器的概念纳入编辑器/语言工具本身。换句话说,一个 eclipse 或 intellij 插件。注释可用于标识所有内容(注释builder() 方法以表明它创建了一个构建器,注释一个类它是一个构建器类等),然后将构建器中的每个方法注释为可选或不可选,以及多个与否(想象一个构建器方法旨在被多次调用以填充列表)。有了这些信息,插件可以“修复”自动完成对话框以跳过 j.l.Object 中的所有废话,并以粗体显示所有必填字段,所有已设置的内容都显示为灰色,所有选项都以正常颜色显示,并且build 方法在设置所有强制项之前显示为灰色,一旦设置为粗体。这才是真正的解决方案。我邀请任何人来编写这些插件,而不是浪费时间维护这个荒谬的 API 盛会来创建一个受阻的想法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多