2016-12-18 14 views
0

프로젝트 중 하나에서 사용중인 빌더 클래스가 있습니다.오래된 빌더를 복제하여 새 빌더 객체를 만드는 방법은 무엇입니까?

  • 아래 클래스를 기반으로 작성자로 metricA이 있다고 가정 해 봅니다.
  • 나는 metricBmetricA 거기에 이미 있던 모든 값을 포함하도록 metricA를 복제하여 metricA을 기반으로 새 빌더 metricB을 확인해야합니다.

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 
} 

질문 : - :

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

지금이 내가를 복제하는 방법이다 아래

내가 metricA 빌더 객체를 만드는 방법이다 metricA 나중에 다른 모든 코드를 얻을 때 코드에 객체가 있습니다. 같은 DS는 다음과 같습니다 :의 MetricHolder 생성자 내 SendData.getInstance() 라인이 두 번 호출됩니다 모든

  • 첫째 : 지금이 문제를 볼

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

    . 먼저 metricA을 만들고 두 번째를 만들 때 metricBmetricA으로 복제하면됩니다. 하지만 난 한 번metricA 빌더 개체를 만들려고 전화하고 싶은거야? 어떻게하면 가능합니까?

  • 두 번째로, 내가 채우는 방법은 MetricHolder 생성자에서 두 개의 필수 입력란을 가진지도가 나에게 보이지 않는다. 같은 일을하는 다른 더 좋은 방법이 있습니까?

은 내가 metricA를 복제하고있는 방법은 metricB 빌더 객체를 만들기 위해 때문에 전체 문제가 일어나고있는 것 같아요? 이 작업을 수행하는 가장 좋은 방법은 무엇입니까? 나는 위의 두 가지를 성취하고자하지만 옳은 방법으로하고 싶다.

+0

불필요한 내용이 많기 때문에 질문이 명확하지 않습니다 (관련성이없는 분야 및 설정자 등). 코드의 핵심 부분이 더 분명해 지도록 [mcve]로 옮길 수 있습니까? –

답변

0

metricA 빌더 객체를 만들 때 한 번만 호출하고 싶습니다. 어떻게하면 가능합니까?

if (!builder.cloned) { 
    SendData.getInstance().whatever(); 
} 
: MetricHolder의 생성자에서,

class Builder { 
    final boolean cloned; 

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

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

다음 :

가장 간단한 방법은이 Record 또는 복제에 의해 만들어 졌는지 여부를 나타내는 빌더에 플래그를하는 것입니다

SendData을 호출하면 doing too much work in the constructor의 예가 될 수 있습니다. 생성자에서이 호출을 실제로 만들고 싶은지 또는 다른 방법으로 그 호출을 고려할 수 있는지 여부에 대해 신중하게 생각해야합니다.

둘째로, MetricHolder 생성자의 두 필수 입력란에 clientPayload 맵을 채우는 방식이 나에게 맞지 않습니다. 같은 일을하는 다른 더 좋은 방법이 있습니까?

당신은 Collections.unmodifiableMap를 사용하는 "불가능한"비트를 오해 한 : 그것은 단지 맵 매개 변수의 변경 불가능한 보기이다; 여전히 기본지도를 수정할 수 있습니다. 여기

을 보여주기 위해 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지도를 수정 불가능한 컬렉션에 배치하기 만하면됩니다. 따라서 이전에 생성 된 인스턴스의 값을 덮어 씁니다. 예를 들어

: builder.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")); 

올바른 접근 방법은 아니다. 지도의 사본을 가지고 그것을 수정 한 다음 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의 범위를 제한 그래서 당신은 실수로 그것을 나중에 다시 사용할 수 없습니다 생성자.