Улучшить шаблон построителя при проверке достоверности?

Недавно я начал использовать шаблон 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, а остальные из них являются необязательными, но мне нужно установить либо userid, flowid, либо deviceid. Если ни один из этих трех не установлен, я выдаю IllegalStateException с сообщением об ошибке, как показано выше в коде. Эту проверку я делаю во время выполнения. Я хочу выполнить эту проверку во время компиляции, если это возможно, и не строить свой шаблон, если все не предусмотрено?

Необязательно, чтобы они передавали все эти три идентификатора каждый раз, они могут передавать все три, иногда два, а иногда только один, но условие состоит в том, что либо один из них должен быть установлен.

Как я могу улучшить свой шаблон построителя, чтобы я мог выполнять проверку идентификатора только во время компиляции, а не во время выполнения?

Я обнаружил эту SO ссылку, которая точно говорит об одном и том же, но не уверен, как могу я использовать это в моем сценарии? А также этот шаблон строителя с изюминкой и этот вопрос SO

Может ли кто-нибудь привести пример, как я могу исправить это в моем шаблоне построителя?


person john    schedule 26.12.2015    source источник
comment
В шаблоне построителя с изюминкой есть Часть 2, которая, кажется, выполняет проверку во время компиляции ... Вы читали эту часть?   -  person Fuhrmanator    schedule 27.12.2015
comment
Почему вы продублировали вопрос: stackoverflow.com/q/34466240/1168342?   -  person Fuhrmanator    schedule 27.12.2015


Ответы (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}
person ankhzet    schedule 26.12.2015
comment
Можете ли вы предоставить примерную основу для моего класса, если возможно, просто чтобы мне было понятнее? Я немного не понимаю, как это использовать. - person john; 26.12.2015