Arhn - архитектура программирования

Сопоставьте типы объектов с помощью пользовательского конвертера

Я пытаюсь разработать картограф, который будет преобразовывать один тип объекта в другой, показанный на диаграмме ниже:

введите здесь описание изображения

Объект имеет следующую структуру (это не JSON):

{
"type": "DownloadAppComponent",
"name": "Download App",
"contentId": "download-app",
"properties": {
    "iosUrl": "http://apple.com",
    "androidUrl": "http: //google.com",
    "promoText": "Download our app",
    "hidden": false
}

Мое первое решение состояло в том, чтобы иметь сопоставитель для каждого типа, но это потребовало много дублирования кода для сопоставления общих атрибутов (например, имя, тип, contentId).

public DownloadAppComponent map(CmsDocument cmsDocument) {
    DownloadAppComponent downloadAppComponent = new DownloadAppComponent();

    downloadAppComponent.setType(cmsDocument.getType()); // <-- this will be duplicated in each mapper
    downloadAppComponent.setName(cmsDocument.getName()); // <-- this will be duplicated in each mapper
    downloadAppComponent.setContentId(cmsDocument.getText(CONTENT_ID_PATH)); // <-- this will be duplicated in each mapper
    downloadAppComponent.setIosURL(cmsDocument.getText(IOS_URL_PATH));
    downloadAppComponent.setAndroidURL(cmsDocument.getText(ANDROID_URL_PATH));
    downloadAppComponent.setHidden(Boolean.parseBoolean(cmsDocument.getText(HIDE_PATH)));
    downloadAppComponent.setPromoText(cmsDocument.getText(DOWNLOAD_PROMO_TEXT_PATH));

    return downloadAppComponent;
}

Я пытался реорганизовать этот код и придумал универсальный BaseDocumentMapper:

public BaseDocument map(CmsDocument cmsDocument) {
    BaseDocument document = documentsMapperFactory.getMapper(cmsDocument.getType()).map(cmsDocument);
    document.setType(cmsDocument.getType());
    document.setName(cmsDocument.getName());
    document.setContentId(cmsDocument.getText(CONTENT_ID_PATH));
    return document;
}

documentsMapperFactory возвращает конкретный преобразователь, который будет отображать только атрибуты, связанные с типом, и возвращать этот экземпляр объекта.

Однако в наследовании еще больше уровней, и у меня есть только одно значение типа, которое я могу преобразовать в конкретный преобразователь. Поэтому мне придется повторять сопоставление конкретных полей компонента в каждом сопоставителе компонентов. Я подумал, что, поскольку я знаю иерархию, я мог бы создать несколько картографов, которые будут отображать документы сверху вниз, то есть сначала создать DownloadAppComponent, а затем расширить его с помощью компонента, а затем с помощью конкретных свойств BaseDocument. Однако я не нашел никакого хорошего решения, кроме использования абстрактных классов и наследования в картографах.

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

Спасибо.

27.09.2016

  • Ну, я бы сначала попробовал использовать существующие библиотеки, такие как dozer.sourceforge.net. 28.09.2016

Ответы:


1

Есть как минимум три альтернативы. Первый является более общим, оставляя концепцию CmsDocument полностью отделенной от BaseDocument.

Два других варианта связывают классы BaseDocument и CmsDocument, поэтому выбор варианта зависит от дизайна.

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

....

private void setCommonValues(BaseDocument doc, CmsDocument cmsDocument) {
    doc.setType(cmsDocument.getType());
    doc.setName(cmsDocument.getName());
    doc.setContentId(cmsDocument.getText(CONTENT_ID_PATH));
}



public DownloadAppComponent map(CmsDocument cmsDocument) {
    DownloadAppComponent downloadAppComponent = new DownloadAppComponent();

    // Call setCommonValues 
    setCommonValues(downloadAppComponent, cmsDocument);

    downloadAppComponent.setIosURL(cmsDocument.getText(IOS_URL_PATH));
    downloadAppComponent.setAndroidURL(cmsDocument.getText(ANDROID_URL_PATH));
    downloadAppComponent.setHidden(Boolean.parseBoolean(cmsDocument.getText(HIDE_PATH)));
    downloadAppComponent.setPromoText(cmsDocument.getText(DOWNLOAD_PROMO_TEXT_PATH));

    return downloadAppComponent;
}

И аналогично для другой функции

public BaseDocument map(CmsDocument cmsDocument) {
    BaseDocument document = documentsMapperFactory.getMapper(cmsDocument.getType()).map(cmsDocument);

    // Call setCommonValues to remove duplication of code
    setCommonValues(document, cmsDocument);
    return document;
}

Второй вариант

Создайте метод init в классе BaseDocument

private void init(CmsDocument cmsDocument) {
    this.setType(cmsDocument.getType());
    this.setName(cmsDocument.getName());
    this.setContentId(cmsDocument.getText(CONTENT_ID_PATH));
}

И в теле карты

public DownloadAppComponent map(CmsDocument cmsDocument) {
    DownloadAppComponent downloadAppComponent = new DownloadAppComponent();

    // Call init 
    downloadAppComponent.init(cmsDocument);

    downloadAppComponent.setIosURL(cmsDocument.getText(IOS_URL_PATH));
    downloadAppComponent.setAndroidURL(cmsDocument.getText(ANDROID_URL_PATH));
    downloadAppComponent.setHidden(Boolean.parseBoolean(cmsDocument.getText(HIDE_PATH)));
    downloadAppComponent.setPromoText(cmsDocument.getText(DOWNLOAD_PROMO_TEXT_PATH));

    return downloadAppComponent;
}

И аналогично для другой функции

public BaseDocument map(CmsDocument cmsDocument) {
    BaseDocument document = documentsMapperFactory.getMapper(cmsDocument.getType()).map(cmsDocument);

    // Call init 
    document.init(cmsDocument);

    return document;
}

Третий вариант

Создайте конструктор для BaseDocument, приняв CmsDocument в качестве параметра

public BaseDocument(CmsDocument cmsDocument) {
    this.setType(cmsDocument.getType());
    this.setName(cmsDocument.getName());
    this.setContentId(cmsDocument.getText(CONTENT_ID_PATH));
}

И в DownloadAppComponent

 public DownloadAppComponent(CmsDocument cmsDocument) {
     super(cmsDocument);
     this.setIosURL(cmsDocument.getText(IOS_URL_PATH));
     this.setAndroidURL(cmsDocument.getText(ANDROID_URL_PATH));
     this.setHidden(Boolean.parseBoolean(cmsDocument.getText(HIDE_PATH)));
    this.setPromoText(cmsDocument.getText(DOWNLOAD_PROMO_TEXT_PATH));

 }

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


Если вам нужен один класс с методом map, который может возвращать два разных экземпляра, вы можете передать требуемый тип в качестве параметра:

public class Mapper {
    private void setCommonValues(BaseDocument doc, CmsDocument cmsDocument) {
        doc.setType(cmsDocument.getType());
        doc.setName(cmsDocument.getName());
        doc.setContentId(cmsDocument.getText(CONTENT_ID_PATH));
    }

    public BaseDocument map(CmsDocument cmsDocument, Class<? extends BaseDocument> clazz) {
        BaseDocument doc = null;
        if (clazz.getCanonicalName().equals(DownloadAppComponent.class.getCanonicalName()) {
            DownloadAppComponent appComponent = new DownloadAppComponent();
            doc = appComponent;
            appComponent.setIosURL(cmsDocument.getText(IOS_URL_PATH));
            appComponent.setAndroidURL(cmsDocument.getText(ANDROID_URL_PATH));
            appComponent.setHidden(Boolean.parseBoolean(cmsDocument.getText(HIDE_PATH)));
            appComponent.setPromoText(cmsDocument.getText(DOWNLOAD_PROMO_TEXT_PATH));

        } else {
             doc = new BaseDocument();
        }
        setCommonValues(doc);
        return doc;
    }
}

Вы можете вызвать его как:

Mapper mapper = new Mapper();
CmsDocument cmsDocument = ...

BaseDocument doc = mapper.map(cmsDocument, BaseDocument.class);

DownloadAppComponent downloadAppComponent = (DownloadAppComponent) mapper.map(cmsDocument, DownloadAppComponent.class);
27.09.2016
  • Я должен сказать, что мне больше нравится первый метод, потому что, как вы сказали, он не делает эти два класса зависимыми и помогает сделать код чище, сохраняя принципы SoC и Single Responsibility. Тем не менее я не понимаю, куда я должен поместить этот метод setCommonValues(). Они не могут быть все в одном классе, потому что он не будет компилироваться. Метод map() был бы неоднозначным. 28.09.2016
  • Вспомогательный метод должен вызываться как super.setCommonValues(), но я должен добавить еще один уровень наследования для классов преобразователя (т.е. DownloadAppComponentMapper extends ComponentMapper и ComponentMapper extends BaseDocumentMapper и т. д.). Могу ли я как-то этого избежать? 28.09.2016
  • @MariuszMiesiak методы map можно переименовать, если их необходимо добавить в тот же класс. Но вы также можете создать для них два класса, не переименовывая его. Если вы выберете один класс с двумя переименованными методами map, вы можете добавить к этому классу метод setCommonValues. В противном случае лучшим решением будет создать абстрактный класс с конкретным методом setCommonValue и абстрактным методом map и создать два подкласса для каждой пользовательской реализации map. 28.09.2016
  • @MariuszMiesiak, если вам нужно, чтобы только один класс и один метод передавали параметр с типом создаваемого класса. Я добавляю это решение к ответу. 28.09.2016
  • Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..