如何克隆旧构建器以创建新的构建器对象? [英] How to clone old builder to make a new builder object?

查看:128
本文介绍了如何克隆旧构建器以创建新的构建器对象?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个我在其中一个项目中使用的构建器类。

I have a builder class which I am using in one of my project.


  • 假设我有 metricA 作为基于以下类的构建器。

  • 我需要基于<创建一个新构建器 metricB code> metricA 通过克隆 metricA ,以便 metricB 包含所有值已经存在 metricA

  • Let's say I have metricA as builder based on below class.
  • I need to make a new builder metricB based on metricA by cloning metricA so that metricB contains all the values which were already there in metricA.

在<$ c $的构造函数中c> MetricHolder 我正在基于已经设置的字段初始化一些字段(没有直接设置)。

In the constructor of MetricHolder I am initializing some fields (which are not set directly) basis on fields that have been set already.


  • clientTypeOrPayId - 我正在初始化此字段。如果 payId 存在,那么我将设置此值,或者我将设置 clientType

  • clientKey - 我也在同一个构造函数中初始化这个字段。

  • 最重要的是,我放的很少 clientPayload 地图中的必填字段。我不确定这样做的正确方法是什么。但我需要在地图中添加 is_clientid is_deviceid 。 (一般来说,我添加了更多的字段)。

  • 然后在构造函数的最后一个,我计算延迟差异并将其发送到其他系统。

  • clientTypeOrPayId - I am initializing this field. If payId is present, then I will set this value or I will set clientType.
  • clientKey - I am initializing this field as well in the same constructor.
  • And most importantly, I am putting few mandatory fields in the clientPayload map. I am not sure what is the right way to do that. But I need to add is_clientid and is_deviceid into the map. (In general I am adding few more fields).
  • And then in the last of the constructor, I am calculating latency difference and sending it to some other system.

以下是我的班级:

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 构建器对象:

Below is how I make metricA builder object:

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

现在我就是克隆 metricA 的方法当我得到所有其他字段时,代码稍后会出现如下所示:

Now this is how I clone the metricA object later on in the code when I get all other fields as shown below:

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

我现在看到两个问题:


  • 首先, MetricHolder 构造函数中的 SendData.getInstance()行将是叫了两次。首先是当我使用 metricA 时,我通过克隆 metricA metricB C $ C>。但是当我尝试创建 metricA 构建器对象时,我只想将其称为 ?我怎样才能做到这一点?

  • 其次,我填充 clientPayload 的方式在<$ c中有两个必填字段$ c> MetricHolder 构造函数看起来不对我。有没有其他更好的方法来做同样的事情?

  • First of all, my SendData.getInstance() line in the MetricHolder constructor will be called twice. First is when I make metricA and second when I make metricB by cloning metricA. But I just want to call it only once when I try to create metricA builder object? How can I make this possible?
  • Second is, the way I am populating clientPayload map with two mandatory fields in the MetricHolder constructor doesn't look right to me. Is there any other better way to do the same thing?

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

I guess the whole problem is happening because the way I am cloning metricA to make a metricB builder object? What is the best way to do this? I want to achieve above two things but in a right way.

推荐答案


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

But I just want to call it only once when I try to create metricA builder object? How can I make this possible?

最直接的方法是在构建器中有一个标志,指示它是否由<$创建c $ c>记录或克隆:

The most straightforward way is to have a flag in the builder indicating whether it was created by Record or by cloning:

class Builder {
  final boolean cloned;

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

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

然后,在 MetricHolder 的构造函数中:

Then, in the constructor of MetricHolder:

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

但值得指出的是,拨打 SendData 在构造函数中做太多工作。您应该仔细考虑是否确实要在构造函数中进行此调用,或者是否可以将其分解为另一种方法。

But it's worth pointing out that making this call to SendData is an example of doing too much work in the constructor. You should think carefully about whether you really want to be making this call in the constructor, or whether you can factor that out into another method.


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

Second is, the way I am populating clientPayload map with two mandatory fields in the MetricHolder constructor doesn't look right to me. Is there any other better way to do the same thing?

你误解了使用<$ c $的不可修改的一点c> Collections.unmodifiableMap :它只是map参数的不可修改的视图;你仍然可以修改基础地图。

You've misunderstood the "unmodifiable" bit of using Collections.unmodifiableMap: it's only an unmodifiable view of the map parameter; you can still modify the underlying map.

这是一个JUnit测试来证明:

Here's a JUnit test to demonstrate:

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());

问题是如果地图上没有其他参考,地图只能是不可修改的:如果你没有引用原始不可修改实际上是不可修改的;否则,你不能依赖地图永远不会改变。

The thing is that the map is only unmodifiable if there is no other reference to it hanging around: if you don't have a reference to original, unmodifiable actually is unmodifiable; otherwise, you can't rely upon the map never changing.

在你的特定情况下,你只需要包装 clientPayload 在您的不可修改的集合中映射。因此,您将覆盖以前构造的实例的值。

In your particular case, you are simply wrapping the clientPayload map in your unmodifiable collection. So, you're overwrite values for previously-constructed instances.

例如:

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 换行:

The correct approach is not to wrap builder.clientPayload. Take a copy of the map, modify it, and then wrap with 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 的范围,因此您不能在构造函数中意外地重复使用它。

The surrounding {} aren't strictly necessary, but they restrict the scope of copyOfClientPayload, so you can't accidentally reuse it later in the constructor.

这篇关于如何克隆旧构建器以创建新的构建器对象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆