Инструменты для построения строк Java на основе шаблонов - PullRequest
0 голосов
/ 08 ноября 2018

Я пытаюсь провести рефакторинг устаревшего кода. Задача здесь состоит в том, чтобы создать длинные сообщения / строки на основе некоторого предварительно определенного шаблона, который выглядит следующим образом:

field1,8,String
filed2,5,Integer
field3,12,String
......

Затем мне передают объект Java, который имеет все эти поля. Здесь нужно просто получить данные из полей объекта и использовать их для построения длинного сообщения / строки на основе шаблона. Некоторые из этих полей также могут быть преобразованы на основе некоторых простых правил. Например:

abc => a
def => d
ghi => g

В результате нам нужно время от времени проверять значения этих полей. Также есть правила заполнения (в основном добавление пустого пространства справа). Таким образом, conostructed сообщение / строка может выглядеть так:

uater   4751 enterprise  ......

В настоящее время мы просто используем грубую силу, чтобы сделать эту работу. Сначала мы передаем шаблон в ArrayList, каждый элемент представляет собой строку, например, «field1,8, String». Во время фактического создания сообщения мы перебираем этот ArrayList, а затем заполняем данные в StringBuffer. Вот пример кода

StringBuffer message = new StringBuffer(1000);
for (String field : templateFields) {
    String[] fieldArray = field.split(Constants.SEPARATOR);
    if (fieldArray[0].equalsIgnoreCase(Constants.WORKFLOW)) {
        message.append(rightPad(object.getFieldOne(), Integer.parseInt(fieldArray[1])));
    } else if (fieldArray[0].equalsIgnoreCase(Constants.WORKVOLUME)) {
        message.append(rightPad(object.getFieldTwo(), Integer.parseInt(fieldArray[1]));
    } else if (fieldArray[0].equalsIgnoreCase(Constants.WORKTYPE)) {
        if (object.getFieldThree().equalsIgnoreCase("abc")) {
             message.append(rightPad("a", Integer.parseInt(fieldArray[1]));
        } else if (object.getFieldThree().equalsIgnoreCase("def")) {
             message.append(rightPad("d", Integer.parseInt(fieldArray[1]));
        } else {
            message.append(rightPad("g", Integer.parseInt(fieldArray[1]));
        }
    } else if ......
}

Как видите, как бы это ни было глупо, оно выполняет свою работу. Но такой код подвержен ошибкам, и его трудно поддерживать. Интересно, есть ли у вас, ребята, какие-нибудь инструменты или библиотеки или какие-нибудь изящные решения, чтобы рекомендовать.
Большое спасибо! Hua

Ответы [ 2 ]

0 голосов
/ 08 ноября 2018

Если я правильно понимаю ваш вопрос, у вас есть подход, при котором вы зацикливаетесь на возможном templateFields. Это не обязательно.

Поскольку каждое fieldArray[0] сравнивается с некоторыми Constants значениями и в случае дальнейшей обработки совпадения мы можем заменить цикл for на Map. Его ключами являются возможные значения Constants, а значениями являются сопоставители. Картограф - это BiFunction, который принимает object и значение fieldArray[1] и возвращает для них сообщение типа String.

Давайте начнем с картографов:

public class FieldToMessageMapper {

    private static final Map<String, Function<String, String>> WORKINGTYPE_MESSAGE_MAPPER = new HashMap<>();
    static {
      WORKINGTYPE_MESSAGE_MAPPER.put("abc", fieldArray1 -> rightPad("a", Integer.parseInt(fieldArray1)));
      WORKINGTYPE_MESSAGE_MAPPER.put("def", fieldArray1 -> rightPad("d", Integer.parseInt(fieldArray1)));
      WORKINGTYPE_MESSAGE_MAPPER.put("DEFAULT", fieldArray1 -> rightPad("g", Integer.parseInt(fieldArray1)));
    }

    private static Map<String, BiFunction<MyObject, String, String>> MESSAGE_MAPPER = new HashMap<>();
    static {
      MESSAGE_MAPPER.put(Constants.WORKFLOW, (o, fieldArray1) -> rightPad(o.getFieldOne(), Integer.parseInt(fieldArray1)));
      MESSAGE_MAPPER.put(Constants.WORKVOLUME, (o, fieldArray1) -> rightPad(o.getFieldTwo(), Integer.parseInt(fieldArray1)));
      MESSAGE_MAPPER.put(Constants.WORKTYPE,
        (o, fieldArray1) -> WORKINGTYPE_MESSAGE_MAPPER.getOrDefault(o.getFieldThree().toLowerCase(), WORKINGTYPE_MESSAGE_MAPPER.get("DEFAULT")).apply(fieldArray1));
    }

    public static Optional<String> map(MyObject o, String fieldArray0, String fieldArray1) {
      return Optional.ofNullable(MESSAGE_MAPPER.get(fieldArray0.toLowerCase()))
        .map(mapper -> mapper.apply(o, fieldArray1));
    }

    private static String rightPad(String string, int pad) {
        // TODO right pad
        return string;
    }
  }

Мы не возвращаем сам маппер. FieldToMessageMapper предлагает метод map, который выполняет отображение. Он возвращает Optional<String>, который показывает, что результат может быть пустым, если нет сопоставления для ввода.
Чтобы получить отображение, независимое от регистра символов, все ключи String..toLowerCase().

Давайте продолжим общую обработку:

  protected StringBuffer process(Collection<String> templateFields, MyObject object) {
    StringBuffer message = new StringBuffer(1000);
    for (String field : templateFields) {
      String[] fieldArray = field.split(Constants.SEPARATOR);
      String msg = FieldToMessageMapper.map(object, fieldArray[0], fieldArray[1])
        .orElseThrow(() -> new IllegalArgumentException(String.format("Unsupported field %s", field)));
      message.append(msg);
    }
    return message;
  }

Я не знаю, как вам нужно обрабатывать пропущенные отображения. Я выбираю провал быстро, выбрасывая исключение.

Обратите внимание: StringBuffer is

Потокобезопасная, изменяемая последовательность символов. Строковый буфер похож на String, но может быть изменено.

Если ваша обработка не многопоточная, вы можете использовать StringBuilder. Если результат больше не изменяется, вы можете использовать String.

Позвольте мне показать еще одну альтернативу, используя Stream, которая возвращает String:

  protected String process(Collection<String> templateFields, MyObject object) {
    return templateFields.stream()
      .map(field -> field.split(Constants.SEPARATOR))
      .map(fieldArray -> FieldToMessageMapper.map(object, fieldArray[0], fieldArray[1])
        .orElseThrow(() -> new IllegalArgumentException(String.format("Unsupported field %s", Arrays.toString(fieldArray)))))
      .collect(Collectors.joining());
  }

Если я правильно понял код вопроса, должна быть следующая реализация Constants:

  public class Constants {
    public static final String SEPARATOR = ",";
    public static final String WORKFLOW = "field1";
    public static final String WORKVOLUME = "filed2";
    public static final String WORKTYPE = "field3";
  }

РЕДАКТИРОВАТЬ:

Если вы хотите использовать конфигурационный подход, вы можете дополнительно разработать этот код, чтобы использовать конфигурацию Spring:

  1. Определите интерфейс MessageMapper, который имеет два метода: String getKey() и String map(MyObject o, String fieldArray1). getKey() возвращает значение Constants, для которого сопоставитель обеспечивает сопоставление.
  2. Реализуйте каждый из вышеперечисленных MESSAGE_MAPPER, используя этот интерфейс.
  3. Добавьте CommonMessageMapper, у которого есть конструктор CommonMessageMapper(MessageMapper... messageMappers). messageMappers должен быть помещен в Map<String, BiFunction<MyObject, String, String>> mappers как: mappers.put(messageMapper.getKey(), messageMapper). Определите метод String map(MyObject o, String fieldArray0, String fieldArray1), который будет искать соответствующий MessageMapper mm, используя fieldArray0: MessageMapper mm = mappers.get(fieldArray0). Затем вызвать mm.map(o, feldArray1). (Вы также можете использовать здесь Optional для обработки случая, когда нет соответствующего картографа.)
  4. Чтобы использовать конфигурацию Spring, все MessageMapper и CommonMessageMapper должны быть аннотированы как Bean или Component. Конструктор CommonMessageMapper должен быть аннотирован @Autowired.
  5. Определите конфигурацию Spring (с использованием XML или как @Configuration), которая будет вводить желаемый MessageMapper в CommonMessageMapper и имеет заводской метод для такого CommonMessageMapper.
  6. Используйте CommonMessageMapper вместо FieldToMessageMapper выше.
0 голосов
/ 08 ноября 2018

Проверьте BeanIO на http://www.beanio.org, чтобы создать текстовые файлы фиксированной ширины.

...