当前位置: 首页 > 面试题库 >

如何克隆旧的构建器以创建新的构建器对象?

西门洛城
2023-03-14
问题内容

我在一个项目中使用了一个生成器类。

  • 假设我有一个metricA基于以下类的构建器。
  • 我需要新的构建metricB基于metricA克隆metricA,使metricB包含所有已经存在的价值metricA

MetricHolder我的构造函数中,我基于已设置的字段初始化一些字段(未直接设置)。

  • clientTypeOrPayId-我正在初始化这个领域。如果payId存在,则将设置该值或将设置clientType
  • clientKey -我也在同一构造函数中初始化此字段。
  • 最重要的是,我在clientPayload地图上放置了一些必填字段。我不确定这样做的正确方法是什么。不过,我需要补充is_clientidis_deviceid成图。(一般而言,我将添加更多字段)。
  • 然后在构造函数的最后一部分中,我要计算延迟差异并将其发送到其他系统

下面是我的课:

public final class MetricHolder {
  private final String clientId;
  private final String deviceId;
  private final String payId;
  private final String clientType;
  private final String clientTypeOrPayId;
  private final Schema schema;
  private final String schemaId;
  private final String clientKey;
  private final Map<String, String> clientPayload;
  private final Record record;
  private final long clientCreateTimestamp;
  private final long clientSentTimestamp;

  private MetricHolder(Builder builder) {
    this.payId = builder.payId;
    this.siteId = builder.siteId;
    this.clientType = builder.clientType;
    this.clientId = builder.clientId;
    this.deviceId = builder.deviceId;
    this.schema = builder.schema;
    this.schemaId = builder.schemaId;
    // populating all the required fields in the map and make it immutable
    // not sure whether this is right?
    builder.clientPayload.put("is_clientid", (clientId == null) ? "false" : "true");
    builder.clientPayload.put("is_deviceid", (clientId == null) ? "true" : "false");
    this.clientPayload = Collections.unmodifiableMap(builder.clientPayload);
    this.clientTypeOrPayId = Strings.isNullOrEmpty(payId) ? clientType : payId;
    this.record = builder.record;
    this.clientKey = "process:" + System.currentTimeMillis() + ":"
                        + ((clientId == null) ? deviceId : clientId);
    this.clientCreateTimestamp = builder.clientCreateTimestamp;
    this.clientSentTimestamp = builder.clientSentTimestamp;
    // this will be called twice while cloning
    // what is the right way to do this then?
    SendData.getInstance().insert(clientTypeOrPayId,
        System.currentTimeMillis() - clientCreateTimestamp);
    SendData.getInstance().insert(clientTypeOrPayId,
        System.currentTimeMillis() - clientSentTimestamp);
  }

  public static class Builder {
    private final Record record;
    private Schema schema;
    private String schemaId;
    private String clientId;
    private String deviceId;
    private String payId;
    private String clientType;
    private Map<String, String> clientPayload;
    private long clientCreateTimestamp;
    private long clientSentTimestamp;

    // this is for cloning
    public Builder(MetricHolder packet) {
      this.record = packet.record;
      this.schema = packet.schema;
      this.schemaId = packet.schemaId;
      this.clientId = packet.clientId;
      this.deviceId = packet.deviceId;
      this.payId = packet.payId;
      this.clientType = packet.clientType;
      // make a new map and check whether mandatory fields are present already or not
      // and if they are present don't add it again.
      this.clientPayload = new HashMap<>();
      for (Map.Entry<String, String> entry : packet.clientPayload.entrySet()) {
        if (!("is_clientid".equals(entry.getKey()) || "is_deviceid".equals(entry.getKey())) {
          this.clientPayload.put(entry.getKey(), entry.getValue());
        }
      }
      this.clientCreateTimestamp = packet.clientCreateTimestamp;
      this.clientSentTimestamp = packet.clientSentTimestamp;
    }

    public Builder(Record record) {
      this.record = record;
    }

    public Builder setSchema(Schema schema) {
      this.schema = schema;
      return this;
    }

    public Builder setSchemaId(String schemaId) {
      this.schemaId = schemaId;
      return this;
    }

    public Builder setClientId(String clientId) {
      this.clientId = clientId;
      return this;
    }

    public Builder setDeviceId(String deviceId) {
      this.deviceId = deviceId;
      return this;
    }

    public Builder setPayId(String payId) {
      this.payId = payId;
      return this;
    }

    public Builder setClientType(String clientType) {
      this.clientType = clientType;
      return this;
    }

    public Builder setClientPayload(Map<String, String> payload) {
      this.clientPayload = payload;
      return this;
    }

    public Builder setClientCreateTimestamp(long clientCreateTimestamp) {
      this.clientCreateTimestamp = clientCreateTimestamp;
      return this;
    }

    public Builder setClientSentTimestamp(long clientSentTimestamp) {
      this.clientSentTimestamp = clientSentTimestamp;
      return this;
    }

    public MetricHolder build() {
      return new MetricHolder(this);
    }
  }

    // getters
}

题:-

下面是我制作metricA构建器对象的方法:

MetricHolder metricA = new MetricHolder.Builder(record).setClientId("123456").setDeviceId("abcdefhg")
                .           setPayId("98765").setClientPayload(payloadMapHolder).setClientCreateTimestamp(createTimestamp)
                            .setClientSentTimestamp(sentTimestamp).build();

现在,这是metricA稍后获得所有其他字段时在代码中克隆对象的方式,如下所示:

MetricHolder metricB = new MetricHolder.Builder(metricA).setSchema(schema).setSchemaId("345").build();

我现在看到两个问题:

  • 首先,我SendData.getInstance()MetricHolder构造函数中的行将被调用两次。首先是当我做克隆metricA时,第二是当我metricB克隆时metricA。但是我只想在尝试创建构建器对象 时才 调用它 一次metricA?我怎样才能做到这一点?
  • 其次,我clientPayloadMetricHolder构造函数中使用两个必填字段填充map 的方式对我来说并不正确。还有其他更好的方法可以做同样的事情吗?

我想整个问题都在发生,因为我正在克隆metricA制作metricB构建器对象的方式吗?做这个的最好方式是什么?我想以正确的方式实现上述两件事。


问题答案:

但是我只想在尝试创建metricA构建器对象时仅调用一次?我怎样才能做到这一点?

最直接的方法是在构建器中具有一个标志,指示该标志是Record通过克隆还是通过克隆创建的:

class Builder {
  final boolean cloned;

  Builder(MetricHolder packet) {
    this.cloned = true;
    // ...
  }

  Builder(Record record) {
    this.cloned = false;
    // ...
  }
}

然后,在的构造函数中MetricHolder

if (!builder.cloned) {
  SendData.getInstance().whatever();
}

但是值得指出的是,进行此调用SendData是在构造函数中进行过多工作的一个示例。您应该仔细考虑是否真的要在构造函数中进行此调用,或者是否可以将其分解为另一个方法。

其次,我在MetricHolder构造函数中用两个必填字段填充clientPayload映射的方式对我来说不合适。还有其他更好的方法可以做同样的事情吗?

您误解了使用的“ Collections.unmodifiableMap无法修改”的地方:它只是map参数的不可修改 视图
;您仍然可以修改基础地图。

这是一个JUnit测试来演示:

Map<String, String> original = new HashMap<>();
original.put("hello", "world");

// Obviously false, we just put something into it.
assertFalse(original.isEmpty());

Map<String, String> unmodifiable = Collections.unmodifiableMap(original);
// We didn't modify the original, so we don't expect this to have changed.
assertFalse(original.isEmpty());
// We expect this to be the same as for the original.
assertFalse(unmodifiable.isEmpty());

try {
  unmodifiable.clear();
  fail("Expected this to fail, as it's unmodifiable");
} catch (UnsupportedOperationException expected) {}

// Yep, still the same contents.
assertFalse(original.isEmpty());
assertFalse(unmodifiable.isEmpty());

// But here's where it gets sticky - no exception is thrown.
original.clear();
// Yep, we expect this...
assertTrue(original.isEmpty());

// But - uh-oh - the unmodifiable map has changed!
assertTrue(unmodifiable.isEmpty());

事实是,只有在没有其他参考时,地图才是不可修改的:如果您没有对的引用original,那么unmodifiable实际上是不可修改的;否则,您将无法依靠地图永远不变。

在特定情况下,您只是将clientPayload地图包装在不可修改的集合中。因此,您将覆盖先前构造的实例的值。

例如:

MetricHolder.Builder builder = new MetricHolder.Builder();
MetricHolder first = builder.build();
assertEquals("false", first.clientPayload.get("is_clientid"));
assertEquals("true", first.clientPayload.get("is_deviceid"));

builder.setClientId("").build();
// Hmm, first has changed.
assertEquals("true", first.clientPayload.get("is_clientid"));
assertEquals("false", first.clientPayload.get("is_deviceid"));

正确的方法是不要包装builder.clientPayload。制作地图副本,对其进行修改,然后使用unmodifiableMap

{
  Map<String, String> copyOfClientPayload = new HashMap<>(builder.clientPayload);
  copyOfClientPayload.put("is_clientid", (clientId == null) ? "false" : "true");
  copyOfClientPayload.put("is_deviceid", (clientId == null) ? "true" : "false");
  this.clientPayload = Collections.unmodifiableMap(copyOfClientPayload);
}

周围环境{}不是严格必需的,但是它们限制了的范围copyOfClientPayload,因此您以后不能在构造函数中意外地重用它。



 类似资料:
  • 问题内容: 我想知道如何创建JSON(JS)对象然后克隆它。 问题答案: 如何在javascript / jquery中创建JSON对象? 没有什么像JSON 对象 。JSON代表 JavaScript Object Notation ,基本上是一个字符串,它编码类似于JavaScript对象常识的信息。 但是,您可以使用创建这种编码(这将导致 字符串 ),请参见JavaScript中的JSON。

  • 问题内容: 我一直认为无需调用构造函数即可创建对象。 但是,在 明智地 阅读《有效的Java 项目11:覆盖克隆》时 ,我发现了一条声明,指出 “没有调用构造函数”的规定太强了。行为良好的克隆方法可以调用构造函数来创建正在构建的克隆内部的对象。如果该类是最终的,则clone甚至可以返回由构造函数创建的对象。 有人可以向我解释一下吗? 问题答案: 我一直以为clone()会创建一个对象而不调用构造函

  • 我一直认为,clone()创建对象时不需要调用构造函数。 但是,在阅读有效Java第11条:明智地覆盖克隆时,我发现了一条声明,上面写着 “不调用构造函数”的规定太强了。行为良好的克隆方法可以调用构造函数来创建正在构建的克隆内部的对象。如果类是最终的,克隆甚至可以返回构造函数创建的对象。 谁能给我解释一下吗?

  • Javassist的API似乎允许我们创建类中声明的类初始值设定项(即静态构造函数)的精确副本: 但是,该副本还包括(公共/私有)静态最终字段。例如,以下类的静态构造函数: 事实上是: 因此,静态构造函数的精确副本也将包括对最终字段“名称”的调用。 有没有办法创建不包含对final字段的调用的静态构造函数的副本? --谢谢

  • 问题内容: Guava为Java类型提供了很棒的工厂方法,例如。 但是,还有Java Maps的构建器吗? 问题答案: 由于Java 9 接口包含: 。 这些工厂方法的局限性在于: 不能将s用作键和/或值(如果您需要存储null,请查看其他答案) 产生 不变的 地图 如果我们需要 可变 地图(例如HashMap),则可以使用其复制构造函数,并让其复制通过创建的地图内容

  • Matisse的第二个主要缺陷是它不够好,您将组件放在网格上,Matisse然后用组件的属性创建一个XML,然后为网格上的组件生成java代码。看起来很酷,但随后您决定要在表单的某个地方添加一个按钮或调整组件的大小--这个过程可能会导致所有的gui混淆,将相邻的组件扔到不同的地方--修复它可能是一个棘手的问题。即使您设法将所有组件放置在它们应该放置的位置,但手动更改了一些生成的netbeans代码