Какой шаблон вы можете посоветовать для решения задачи? - PullRequest
1 голос
/ 25 марта 2019

У меня есть задание, и я хотел бы выяснить, какой шаблон лучше использовать.

Задача довольно проста:

Разработать приложение, которое использует различные текстовые файлы, которые отличаются отклиент-клиент, анализирует их один за другим и сохраняет в базе данных в единой форме.Приложение будет использоваться многими клиентами.

Например, один файл .csv может содержать следующую полезную нагрузку:

day,time,year,name,surname
01,12:00,2019,Andrey,Arshavin
... 

Другой файл может выглядеть следующим образом:

day,time,year,fullName,country
01,12:00,2011,Cristiano Ronaldo,Portugal
...

И таблица базы данных имеет следующие столбцы:

date(Timestamp),firstName(String),secondName(String)

Цель проекта - предоставить решение с четким дизайном.

Первая идея, которая у меня возникла, этосоздать разные DTOs для разных клиентов и один Entity класс.При каждом чтении .csv создайте List<DTO> с помощью заводского метода, а затем преобразуйте его в List<Entity> и сохраните.

Но как я могу сделать его более расширяемым и обслуживаемым?Я думаю, что код спагетти не очень хорошая идея для каждого нового клиента, я должен буду добавить

"if that customer then return such new object"

Ответы [ 2 ]

1 голос
/ 25 марта 2019

Четким решением будет использование какого-то полиморфизма.Вместо использования стороны DTO / Entity, как насчет стороны парсера?

Вам необходимо определить интерфейс IParser с методом List<PersonEntry> parse(String rawFileString)

Каждая реализация длякаждый клиент будет обрабатывать определенный формат.Но каждый раз он выдаст список:

class PersonEntry {
    public int timestamp;
    public String firstName;
    public String secondName;
}

За сохранение его в базе данных будет отвечать какой-то другой класс.Давайте назовем это Repository.

Следующим шагом, чтобы сделать ваше приложение более расширяемым, будет разделение процесса анализа файла CSV на 2 шага:

  1. Анализ строки CSV вкакой-то Map / Dictionary
  2. Извлечение только интересных полей

Я имею в виду, вам понадобятся только экстракторы, такие как:

 class CustomerAExtractor {
     public PersonEntry extract(Map<String, String> csvEntry) {
         PersonEntry personEntry = new PersonEntry();
         personEntry.timestamp = new Date(csvEntry.get("date"), csvEntry.get("time")).getTimestamp();
         String fullNameParts = csvEntry.get("fullName").split(" ");
         personEntry.firstName = fullNameParts[0];
         personEntry.lastName = fullNameParts[1];
         return personEntry;
     }
 }

для каждогоотдельный клиент.

Редактировать

Я только что понял, что ваша основная цель - избегать цепочек if / else для каждого отдельного формата файла.

Есть много способов сделать это.Упомянутый в вашем комментарии - один из них, но он сопряжен с некоторыми рисками (сравнение предопределенного набора строк с фактическим чтением из чувствительности к регистру файлов и т. Д.).

Другие возможности:

  1. Для реализации boolean isCompatibleWith(String fileContent) в каждом Parser классе ведите их список, а затем найдите тот, который возвращает true.
  2. Очень похоже на пункт 1. Схема цепочки ответственности.Каждый анализатор указывает на следующий, чтобы попытаться проанализировать файл на следующем шаге, если текущий не может это сделать.
  3. (Только для форматов CSV) Очень сумасшедшая идея, которая пришлана мой взгляд, сейчас: как насчет создания экстракторов в виде динамически сконструированного набора mixins?Я имею в виду, что заголовок содержит поле fullName, затем введите следующий код:

    public PersonEntry addNextData(PersonEntry partiallyFilled, Map<String, String> csvEntry)
        String fullNameParts = csvEntry.get("fullName").split(" ");
        partiallyFilled.firstName = fullNameParts[0];
        partiallyFilled.lastName = fullNameParts[1];
        return partiallyFilled;
    }
    

Затем определите, что заголовок содержит date и time, и введите кодкоторый может создать из них метку времени и заполнить отсутствующее поле метки времени в классе PersonEntry.

Например:

public class CsvParser implements IParser {
    @Override
    public boolean isCompatibleWith(String content) {
        return isProperCSV(content);
    }

    private boolean isProperCSV(String content) {
        String[] lines = content.split("\n");
        int headerSize = lines[0].split(",").length;
        for (String line : lines) {
            if (line.split(",").length != headerSize) return false;
        }
        return true;
    }

    @Override
    public List<PersonEntry> parse(String content) {
        IExtractor extractor = buildExtractor(cells.get(0).keySet());
        List<Map<String, String>> cells = new CsvIntoMap().parse(content);
        return cells.stream()
            .map(row -> extractor.extract(row))
            .collect(Collectors::toList);
    }

    private IExtractor buildExtractor(Collection<String> header) {
        List<ExtractorMixin> mixins = new LinkedList<>();
        if (header.containsIgnoreCase("fullName")) {
            mixins.add(new FullNameExtractorMixin());
        }
        .....
        return new ExtractorFromMixins(mixins);
    }
}

public class ExtractorFromMixins implements IExtractor {
    private List<ExtractorMixin> mixins;

    public ExtractorFromMixins(List<ExtractorMixin> mixins) {
         this.mixins = mixins;
    }

    @Override
    public PersonEntry extract(Map<String, String> row) {
         PersonEntry personEntry = new PersonEntry();
         this.mixins.stream()
             .forEach(mixin -> personEntry = mixin.addNextData(personEntry, row));
         return personEntry;
    }
}
1 голос
/ 25 марта 2019

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


Первая идея, которая у меня есть, - создавать разные DTO для разных клиентов

Учитывая то, что вы написали, это означает, что вы уже знаете формат файла всех ваших клиентов.
Будут ли они меняться со временем?
Можете ли вы добавить новый класс, если появится новый клиент, или есликто-то меняет формат файла?
Если нет, это решение не применимо.

сохраняется в базе данных в единой форме

Это означает, что выиметь одну таблицу базы данных, в которой будут храниться эти данные.
Учитывая, что каждый файл имеет разные заголовки, вам необходимо подготовить таблицу таким образом, чтобы в ней были сохранены все возможные заголовки, или каким-либо образом отобразить их.


Затем необходимо инкапсулировать процесс чтения файла, его анализа и сохранения.
Мое предложение состоит в том, чтобы иметь правильный класс, который получает в качестве входных данных CSVFile экземпляр.
Вот как я представляю поток кода

final File file = ... 

// Program against interfaces
final Parser parser = new CsvParser(file);
final ParseResult result = parser.parse();

// Headers and Rows provide access to each field or row via getters
final Headers headers = result.getHeaders();
final Rows rows = result.getRows();

// Decorator pattern to apply headers mappings, to have a unified layout
final Headers mappedHeaders = new MappingHeaders(headers, mapping);
persistence.save(mappedHeaders, rows);

Для фазы синтаксического анализа простой, хотя и чистый, подход поддерживает каждую строку как Object[] или String[]массив (решите, хотите ли вы применять преобразования или нет).
Как вы знаете, CSV-файл имеет каждое поле, разделенное запятой (,), что означает, что вам нужно

  • прочитать первую строку, разбить ее и сохранить полученный массив в виде заголовков / ключей массива.
    headers = ["day", "time, "year", "name", "surname"]

  • прочитать вторую строку, разбить ее, сохранить в массиве, который будет иметь одинаковую длину заголовков, один

  • повтор для каждой последующей строки

Таким образом, каждый индекс массива соответствует определенному имени столбца, которое можно получить с помощью массива headers.

Что ж, я дал вамобщее видениеТеперь все зависит от вас, поскольку вы знаете требования лучше, чем кто-либо другой.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...