【问题标题】:Step builder pattern using delegation and enums?使用委托和枚举的步骤生成器模式?
【发布时间】:2018-09-14 06:33:20
【问题描述】:

我正在从事这个项目,基本上这就是我想要实现的目标。

这就是我所拥有的:

MyObject obj = MyObject.builder()
                       .withValue("string")
                       .withAnotherValue("string")
                       .build();

MyObject obj = MyObject.builder()
                       .withValue("string")
                       .withAnotherValue("string")
                       .withField("key", "value")
                       .build();

因此步骤构建器模式强制用户按顺序使用withValue() 方法和withAnotherValue() 方法。方法field()是可选的,可以多次使用。我关注了这个网站,例如http://www.svlada.com/step-builder-pattern/

所以我想要实现的是:

MyObject obj = MyObject.builder(Type.ROCK)
                       .withColour("blue")
                       .withValue("string")
                       .withAnotherValue("string")
                       .build();

MyObject obj = MyObject.builder(Type.STONE)
                       .withWeight("heavy")
                       .withValue("string")
                       .withAnotherValue("string")
                       .withField("key", "value")
                       .build();

因此,在 builder() 方法中,您将放置一个枚举类型,并且基于该枚举,您将出现一组不同的方法。所以对于 ROCK,withValue()withAnotherValue()withColour() 现在是强制性的。但对于 STONE,withWeight()withAnotherValue()withColour() 是强制性的。

我这样的事情可能吗?过去两天我一直在尝试解决这个问题,但我似乎无法让它为每种类型提供特定的方法。它只显示了 Builder 中的所有方法。

非常感谢任何想法和帮助。

代码:

枚举

public enum Type implements ParameterType<Type> {

  ROCK, STONE

}

参数类型

interface ParameterType<T> {}

我的对象

public class MyObject implements Serializable {

  private static final long serialVersionUID = -4970453769180420689L;

  private List<Field> fields = new ArrayList<>();

  private MyObject() {
  }

  public interface Type {

    Value withValue(String value);
  }

  public interface Value {

    Build withAnotherValue(String anotherValue);
  }

  public interface Build {

    MyObject build();
  }

  public Type builder(Parameter type) {
    return new Builder();
  }

  public static class Builder implements Build, Type, Value {

    private final List<Field> fields = new ArrayList<>();

    @Override
    public Build withAnotherValue(String anotherValue) {
      fields.add(new Field("AnotherValue", anotherValue));
      return this;
    }

    @Override
    public Value withValue(String value) {
      fields.add(new Field("Value", value));
      return this;
    }

    @Override
    public MyObject build() {
      MyObject myObject = new MyObject();
      myObject.fields.addAll(this.fields);
      return myObject;
    }
  }

}

【问题讨论】:

    标签: java design-patterns enums builder delegation


    【解决方案1】:

    使用enum 无法做到这一点,但您可以使用自定义enum-like 类来做到这一点:

    public final class Type<B extends MyObject.Builder> {
        private final Supplier<? extends B> supplier;
    
        private Type(Supplier<? extends B> supplier) {
            this.supplier = Objects.requireNonNull(supplier);
        }
    
        public B builder() {
            return supplier.get();
        }
    
        public static final Type<MyObject.RockBuilder> ROCK =
            new Type<>(MyObject.RockBuilder::new);
    
        public static final Type<MyObject.StoneBuilder> STONE =
            new Type<>(MyObject.StoneBuilder::new);
    }
    
    public class MyObject {
        // ...
    
        // And this method is probably superfluous at this point.
        public static <B extends MyObject.Builder> builder(Type<? extends B> type) {
            return type.builder();
        }
    }
    

    您可以轻松地将这种方法应用于步骤构建器,但这里有一个单独的问题。由于步骤构建器中的每个步骤都在返回类型中指定了下一步,因此您不能很容易地重用步骤接口。例如,您需要声明单独的接口RockValueStepStoneValueStep 等,因为接口本身指定了步骤顺序。

    解决这个问题的唯一简单方法是,如果单独的类型(岩石、石头等)仅严格添加步骤,例如Type.ROCK 返回 ColourStepType.STONE 返回 WeightStepColourStepWeightStep 返回 ValueStep

    // Rock builder starts here.
    interface ColourStep { ValueStep withColour(String c); }
    // Stone builder starts here.
    interface WeightStep { ValueStep withWeight(String w); }
    // Shared.
    interface ValueStep { AnotherValueStep withValue(String v); }
    

    然后:

    public final class Type<B /* extends ABuilderStepMarker, possibly */> {
        // (Constructor and stuff basically same as before.)
    
        public static final Type<MyObject.ColourStep> ROCK =
            new Type<>(/* implementation */::new);
    
        public static final Type<MyObject.WeightStep> STONE =
            new Type<>(/* implementation */::new);
    }
    

    这种事情用enum做不到的原因很多:

    • enum 不能是通用的:

      // This is an error.
      enum Type<T> {
      }
      
    • 虽然您可以在 enum 上声明一个抽象方法并用协变返回类型覆盖它,但协变返回类型永远不可见:

      // This is valid code, but the actual type of
      // Type.ROCK is just Type, so the return type of
      // Type.ROCK.builder() is just MyObject.Builder,
      // despite the override.
      enum Type {
          ROCK {
              @Override
              public MyObject.RockBuilder builder() {
                  return new MyObject.RockBuilder();
              }
          };
          public abstract MyObject.Builder builder();
      }
      

    【讨论】:

    • 感谢您的回复。我评论了贾斯汀·阿尔巴诺(Justin Albano)对我所采用的实施的回答。我可能会用我所做的实现来更新这个问题......很快。再次感谢您的帮助。
    【解决方案2】:

    考虑到您正在为特定类型的构建器寻找特定方法,有多个构建器,每个可构建的MyObject 类型一个可能效果最佳。您可以创建一个定义构建器的接口,然后将通用功能放入一个抽象类,各个构建器从该类扩展。例如:

    public interface Builder {
        public MyObject build();
    }
    
    public abstract class AbstractBuilder() {
    
        private final List<Field> fields = new ArrayList<>();
    
        protected void addField(String key, String value) {
            fields.add(new Field(key, value));
        }
    
        @Override
        public MyObject build() {
            MyObject myObject = new MyObject();
            myObject.fields.addAll(this.fields);
            return myObject;
        }
    }
    
    public class StoneBuilder extends AbstractBuilder {
    
        public StoneBuilder withValue(String value) {
            addField("Value", value);
            return this;
        }
    
        // ...More builder methods...
    }
    
    public class RockBuilder extends AbstractBuilder {
    
        public RockBuilder withAnotherValue(String value) {
            addField("AnotherValue", value);
            return this;
        }
    
        // ...More builder methods...
    }
    

    这允许您以下列方式构建MyObject 实例:

    MyObject obj = new RockBuilder()
                       .withValue("string")
                       .build();
    
    MyObject obj = new StoneBuilder()
                       .withAnotherValue("string")
                       .build();
    

    【讨论】:

    • 我实现了与此类似的想法。我为每种类型创建了一个单独的构建器,并创建了一个接口来返回具有所需不同步骤接口的不同类型的构建器。所以现在我可以这样做:RockBuilder.ofRock().withColour("string").withValue("string").build();RockBuilder.ofStone().withWeight("string").withValue("string").build(); 谢谢你的回复。
    【解决方案3】:

    你的问题可以概括如下:“我怎样才能写出下面的方法?”

    public <T extends AbstractBuilder> T builder(final SomeNonGenericObject object) {
        // code goes here
    }
    

    答案是:“你不能,因为编译器无法推断T的类型是什么。唯一可行的方法是通过某种方式传递@ 987654324@作为参数:

    public <T extends AbstractBuilder> T builder(final SomeNonGenericObject object, final Class<T> builderClass) {
        // code goes here
    }
    

    public <T extends AbstractBuilder> T builder(final SomeGenericObject<T> object) {
        // code goes here
    }
    

    例如:

    public <T extends AbstractBuilder> T builder(final Supplier<T> object) {
        return supplier.get();
    }
    
    final Supplier<AbstractBuilder> rockBuilderSupplier = RockBuilder::new;
    builder(rockBuilerSupplier)
        .withColour("blue")
        // etc
    

    或者简单地使用贾斯汀阿尔巴诺的答案,它也同样有效。

    【讨论】:

    • 感谢您的回答。我评论了贾斯汀·阿尔巴诺(Justin Albano)对我所采用的实施的回答。我可以用我所做的实现来更新问题......很快。再次感谢您的帮助。
    • 只是提示,您可以使用 AOP 编译时编织或 AnnotationProcessors 来创建从运行时到编译时的桥梁。一个好的阅读起点是baeldung.com/java-annotation-processing-builder
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-27
    • 1970-01-01
    • 2011-08-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-25
    相关资源
    最近更新 更多