在验证检查中改进构建器模式?

时间:2015-12-26 04:44:35

标签: java design-patterns builder-pattern

我最近在我的一个项目中开始使用Builder模式,我正在尝试在Builder类中添加某种验证。我假设我们不能在编译时这样做,所以这就是我在运行时进行此验证的原因。但可能是我错了,这就是我想看看我是否可以在编译时这样做。

public final class RequestKey {

    private final Long userid;
    private final String deviceid;
    private final String flowid;
    private final int clientid;
    private final long timeout;
    private final boolean abcFlag;
    private final boolean defFlag;
    private final Map<String, String> baseMap;

    private RequestKey(Builder builder) {
        this.userid = builder.userid;
        this.deviceid = builder.deviceid;
        this.flowid = builder.flowid;
        this.clientid = builder.clientid;
        this.abcFlag = builder.abcFlag;
        this.defFlag = builder.defFlag;
        this.baseMap = builder.baseMap.build();
        this.timeout = builder.timeout;
    }

    public static class Builder {
        protected final int clientid;
        protected Long userid = null;
        protected String deviceid = null;
        protected String flowid = null;
        protected long timeout = 200L;
        protected boolean abcFlag = false;
        protected boolean defFlag = true;
        protected ImmutableMap.Builder<String, String> baseMap = ImmutableMap.builder();

        public Builder(int clientid) {
            checkArgument(clientid > 0, "clientid must not be negative or zero");
            this.clientid = clientid;
        }

        public Builder setUserId(long userid) {
            checkArgument(userid > 0, "userid must not be negative or zero");
            this.userid = Long.valueOf(userid);
            return this;
        }

        public Builder setDeviceId(String deviceid) {
            checkNotNull(deviceid, "deviceid cannot be null");
            checkArgument(deviceid.length() > 0, "deviceid can't be an empty string");
            this.deviceid = deviceid;
            return this;
        }

        public Builder setFlowId(String flowid) {
            checkNotNull(flowid, "flowid cannot be null");
            checkArgument(flowid.length() > 0, "flowid can't be an empty string");
            this.flowid = flowid;
            return this;
        }

        public Builder baseMap(Map<String, String> baseMap) {
            checkNotNull(baseMap, "baseMap cannot be null");
            this.baseMap.putAll(baseMap);
            return this;
        }

        public Builder abcFlag(boolean abcFlag) {
            this.abcFlag = abcFlag;
            return this;
        }

        public Builder defFlag(boolean defFlag) {
            this.defFlag = defFlag;
            return this;
        }

        public Builder addTimeout(long timeout) {
            checkArgument(timeout > 0, "timeout must not be negative or zero");
            this.timeout = timeout;
            return this;
        }

        public RequestKey build() {
            if (!this.isValid()) {
                throw new IllegalStateException("You have to pass at least one"
                        + " of the following: userid, flowid or deviceid");
            }
            return new RequestKey(this);
        }

        private boolean isValid() {
            return !(TestUtils.isEmpty(userid) && TestUtils.isEmpty(flowid) && TestUtils.isEmpty(deviceid));
        }
    }

    // getters here
}

问题陈述:

在我上面的构建器模式中,我只有一个参数必需clientId,其余参数是可选的,但我需要设置useridflowiddeviceid 。如果这三个都没有设置,那么我向IllegalStateException抛出一条错误消息,如上面的代码中所示。那个检查我在运行时做的。如果可能的话,我想在编译时进行此检查,除非提供所有内容,否则不要构建我的模式?

并非强制要求他们每次都会传递所有这三个id,他们可以传递所有三个或有时两个或有时只传递一个,但条件是其中一个应该被设置。

如何改进构建器模式,以便我只能在编译时进行id验证,而不是在运行时执行此操作?

我发现了这个SO link,它确实谈到同样的事情,但不确定如何在我的场景中使用它?还有builder pattern with a twist和此SO question

任何人都可以提供一个示例,我如何在构建器模式中修复此问题?

1 个答案:

答案 0 :(得分:1)

只有在设置了必需的属性时才能访问build方法:

public class Builders {

  /**
   * @param args the command line arguments
   */
  public static void main(String[] args) {

    Builder b = new Builder(123);

//    Builded instance1 = b
//      .defFlag(false)
//      .build(); // compile error

    Builder c1 = b
      .refFlag(true);

    Builded instance2 = b
      .setDeviceId("device id") // here's the magic, without this call `build` method would be unaccessible
      .build();

    Builded instance3 = b
      .refFlag(false)
      .defFlag(true)
      .setDeviceId("device id")
      .setUserId(12)
      .build();

    System.out.printf("%s\n%s\n", instance2, instance3);
  }

}

class Builded implements Cloneable {

  int clientId;

  Long userid;
  String deviceid;
  String flowid;

  boolean defFlag;
  boolean refFlag;

  public Builded(int clientId) {
    this.clientId = clientId;
  }

  @Override
  protected Object clone() throws CloneNotSupportedException {
    return (Builded) super.clone();
  }

  @Override
  public String toString() {
    return String.format("{c: %d, u: %d, d: %s, f: %s, df: %b, rf: %b}", clientId, userid, deviceid, flowid, defFlag, refFlag);
  }

}

class Builder {

  int clientId;
  protected Builded instance;

  private Builder() {
  }

  protected Builder(int clientId) {
    this.clientId = clientId;
    prepare();
  }

  protected final void prepare() {
    instance = new Builded(clientId);
  }

  private Builded build() {
    try {
      return (Builded) instance.clone();
    } catch (CloneNotSupportedException ex) {
      throw new RuntimeException(ex);
    }
  }

  public Builder defFlag(boolean defFlag) {
    instance.defFlag = defFlag;
    return this;
  }

  public Builder refFlag(boolean refFlag) {
    instance.refFlag = refFlag;
    return this;
  }

  public SubBuilder setUserId(long userid) {
    instance.userid = userid;
    return new SubBuilder(instance);
  }

  public SubBuilder setDeviceId(String deviceid) {
    instance.deviceid = deviceid;
    return new SubBuilder(instance);
  }

  public SubBuilder setFlowId(String flowid) {
    instance.flowid = flowid;
    return new SubBuilder(instance);
  }

  public static class SubBuilder extends Builder {

    private SubBuilder(Builded instance) {
      this.instance = instance;
    }

    public Builded build() {
      return super.build();
    }

  }

}

输出:

{c: 123, u: null, d: device id, f: null, df: false, rf: true}
{c: 123, u: 12, d: device id, f: null, df: true, rf: false}