Сделать рефакторинг обработки документов

18. Сделать рефакторинг обработки документов

Условие задачи:

Дан сервис обработки документов разных типов. Нужно выполнить рефакторинг, убрать разрастающийся switch и упростить добавление новых форматов.

Код:

public class RefEx {

    public enum DocumentType {
        XML,
        PDF,
        DOCX
    }

    public static class Document {
        String id;
        DocumentType type;
        String content;
    }

    public static class DocumentService {

        public void process(Document[] d) {
            for (Document i : d) {

                // Общая логика обработки документа
                switch (i.type) {
                    case DocumentType.PDF: {
                        // Специфическая логика для обработки PDF
                    } break;

                    case DocumentType.DOCX: {
                        // Специфическая логика для обработки Word
                    } break;

                    case DocumentType.XML: {
                        // Специфическая логика для обработки XML
                    } break;
                }
            }
        }
    }
}

Спойлеры к решению

Подсказки
💡 Имена d и i не отражают смысл переменных.
💡 Нужно проверить массив и его элементы на null.
💡 При добавлении нового типа придётся изменять DocumentService.
💡 Специфическую обработку лучше вынести в отдельные классы.
💡 Для выбора обработчика можно использовать паттерн Strategy.

Решение

Проблемы:

  • DocumentService содержит логику обработки всех форматов;

  • при добавлении нового типа нужно изменять switch — нарушается OCP;

  • общая и специфическая логика смешаны;

  • имена d и i плохо отражают назначение переменных;

  • нет проверки массива, документов и type на null;

  • для enum в обычном switch case следует писать case PDF, а не case DocumentType.PDF;

  • при большом количестве форматов switch будет постоянно разрастаться.

Лучше применить Strategy и вынести обработку каждого формата в отдельный класс.

public interface DocumentProcessor {

    DocumentType getType();

    void process(Document document);
}

Например:

public class PdfDocumentProcessor implements DocumentProcessor {

    @Override
    public DocumentType getType() {
        return DocumentType.PDF;
    }

    @Override
    public void process(Document document) {
        // Специфическая логика обработки PDF
    }
}

Аналогично создаются DocxDocumentProcessor и XmlDocumentProcessor.

Сам сервис отвечает только за выбор стратегии и общую обработку:

public class DocumentService {

    private final Map<DocumentType, DocumentProcessor> processors;

    public DocumentService(
            Collection<DocumentProcessor> processors
    ) {
        this.processors = new EnumMap<>(DocumentType.class);

        for (DocumentProcessor processor : processors) {
            DocumentProcessor previous =
                    this.processors.put(
                            processor.getType(),
                            processor
                    );

            if (previous != null) {
                throw new IllegalArgumentException(
                        "Duplicate processor: "
                                + processor.getType()
                );
            }
        }
    }

    public void process(Document[] documents) {
        if (documents == null) {
            throw new IllegalArgumentException(
                    "Documents must not be null"
            );
        }

        for (Document document : documents) {
            if (document == null || document.type == null) {
                throw new IllegalArgumentException(
                        "Invalid document"
                );
            }

            // Общая логика обработки документа

            DocumentProcessor processor =
                    processors.get(document.type);

            if (processor == null) {
                throw new IllegalArgumentException(
                        "Unsupported document type: "
                                + document.type
                );
            }

            processor.process(document);
        }
    }
}

Теперь для нового формата достаточно добавить новый DocumentProcessor. DocumentService изменять не требуется.

Основная идея рефакторинга: общая логика остаётся в сервисе, а специфическая логика каждого формата переносится в отдельную Strategy.