How to clone old builder to make a new builder object?

前端 未结 1 1025
余生分开走
余生分开走 2020-12-18 17:14

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

  • Let\'s say I have metricA as builder based on below class.
  • I need to make
相关标签:
1条回答
  • 2020-12-18 18:10

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

    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;
        // ...
      }
    }
    

    Then, in the constructor of MetricHolder:

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

    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.

    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?

    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.

    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.

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

    For example:

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

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

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

    0 讨论(0)
提交回复
热议问题