2016-12-18 2 views
0

У меня есть класс строителя, который я использую в одном из моих проектов.Как клонировать старый строитель, чтобы создать новый объект-строитель?

  • Предположим, у меня есть metricA как строитель, основанный на классе ниже.
  • мне нужно сделать новый построитель metricB, основанного на metricA клонировании metricA так, что metricB содержит все значения, которые были там уже в metricA.

В конструкторе MetricHolder Я инициализирую некоторые поля (которые не заданы непосредственно) в соответствии с уже установленными полями.

  • clientTypeOrPayId - Я инициализирую это поле. Если присутствует payId, тогда я установил это значение или установил clientType.
  • clientKey - Я инициализирую это поле также в том же конструкторе.
  • И самое главное, я помещаю несколько обязательных полей на карте clientPayload. Я не уверен, что это правильный способ сделать это. Но мне нужно добавить is_clientid и is_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 объект позже в коде, когда я получаю все остальные поля DS, как показано ниже:

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

Я вижу две проблемы сейчас:

  • Прежде всего, моя SendData.getInstance() линия в MetricHolder конструктор будет вызываться дважды. Во-первых, когда я делаю metricA и второй, когда делаю metricB, клонируя metricA. Но я просто хочу назвать это только один раз при попытке создать metricA объект-строитель? Как я могу сделать это возможным?
  • Во-вторых, путь, который я заполняю clientPayload Карта с двумя обязательными полями в конструкторе MetricHolder выглядит не так. Есть ли другой лучший способ сделать то же самое?

Я предполагаю, что вся проблема происходит потому, что так, как я клонировать metricA сделать объект metricB строителя? Каков наилучший способ сделать это? Я хочу достичь выше двух вещей, но правильно.

+0

Ваш вопрос неясен, потому что в нем много ненужного материала (много полей и сеттеров и т. Д., Которые не выглядят релевантными). Пожалуйста, вы можете разделить его на [mcve], чтобы ключевые части вашего кода были более очевидными? –

ответ

0

Но я просто хочу называть его только один раз, когда я пытаюсь создать объект-объект 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 является примером doing too much work in the constructor. Вы должны тщательно подумать о том, хотите ли вы действительно делать этот вызов в конструкторе, или можете ли вы включить это в другой метод.

Во-вторых, способ заполнения клиентом карты с двумя обязательными полями в конструкторе MetricHolder не подходит для меня. Есть ли другой лучший способ сделать то же самое?

Вы неправильно поняли «неизменяемые» бит использования 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 в своей неизменяемой коллекции. Таким образом, вы переопределяете значения для ранее созданных экземпляров.

Например:

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, так что вы не можете случайно использовать его позже конструктор.

Смежные вопросы